@pheem49/mint 1.3.0 → 1.4.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/.codex +0 -0
- package/README.md +174 -126
- package/main.js +21 -1
- package/mint-cli-logic.js +21 -1
- package/mint-cli.js +287 -45
- package/package.json +13 -2
- package/src/AI_Brain/Gemini_API.js +331 -64
- package/src/AI_Brain/agent_orchestrator.js +73 -0
- package/src/AI_Brain/autonomous_brain.js +2 -0
- package/src/AI_Brain/memory_store.js +318 -0
- package/src/AI_Brain/proactive_engine.js +2 -8
- package/src/Automation_Layer/file_operations.js +123 -4
- package/src/Automation_Layer/open_app.js +72 -43
- package/src/Automation_Layer/open_website.js +3 -3
- package/src/CLI/chat_router.js +57 -9
- package/src/CLI/chat_ui.js +117 -11
- package/src/CLI/code_agent.js +249 -36
- package/src/CLI/onboarding.js +53 -6
- package/src/CLI/workspace_manager.js +90 -0
- package/src/Plugins/docker.js +12 -10
- package/src/Plugins/spotify.js +168 -40
- package/src/Plugins/system_monitor.js +72 -0
- package/src/System/config_manager.js +35 -2
- package/src/System/custom_workflows.js +9 -2
- package/src/System/notifications.js +23 -0
- package/src/UI/settings.html +143 -65
- package/src/UI/settings.js +155 -41
- package/tests/agent_orchestrator.test.js +41 -0
- package/tests/chat_router.test.js +42 -0
- package/tests/code_agent.test.js +69 -0
- package/tests/config_manager.test.js +141 -0
- package/tests/docker.test.js +46 -0
- package/tests/file_operations.test.js +57 -0
- package/tests/memory_store.test.js +185 -0
- package/tests/provider_routing.test.js +67 -0
- package/tests/spotify.test.js +201 -0
- package/tests/system_monitor.test.js +37 -0
- package/tests/workspace_manager.test.js +56 -0
package/src/CLI/code_agent.js
CHANGED
|
@@ -3,13 +3,16 @@ const path = require('path');
|
|
|
3
3
|
const { execFile } = require('child_process');
|
|
4
4
|
const { promisify } = require('util');
|
|
5
5
|
const { GoogleGenAI } = require('@google/genai');
|
|
6
|
-
const
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
7
8
|
const { readWorkspaceSession, writeWorkspaceSession } = require('./code_session_memory');
|
|
8
9
|
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
11
12
|
const MAX_TOOL_OUTPUT = 12000;
|
|
12
13
|
const MAX_AGENT_STEPS = 16;
|
|
14
|
+
const MAX_JSON_REPAIR_ATTEMPTS = 2;
|
|
15
|
+
const SUPPORTED_CODE_PROVIDERS = ['gemini', 'anthropic', 'openai', 'local_openai'];
|
|
13
16
|
|
|
14
17
|
const CODE_AGENT_PROMPT = `You are Mint Code Mode, a careful coding agent for a local workspace.
|
|
15
18
|
|
|
@@ -30,10 +33,11 @@ Rules:
|
|
|
30
33
|
Response format:
|
|
31
34
|
{
|
|
32
35
|
"thought": "short reasoning",
|
|
33
|
-
"action": "list_files" | "read_file" | "search_code" | "run_shell" | "apply_patch" | "write_file" | "finish",
|
|
36
|
+
"action": "list_files" | "read_file" | "search_code" | "find_path" | "run_shell" | "apply_patch" | "write_file" | "finish",
|
|
34
37
|
"input": {
|
|
35
38
|
"path": "relative/path",
|
|
36
39
|
"query": "search text",
|
|
40
|
+
"type": "file" | "dir" | "any",
|
|
37
41
|
"command": "shell command",
|
|
38
42
|
"startLine": 1,
|
|
39
43
|
"endLine": 120,
|
|
@@ -57,6 +61,7 @@ Tool notes:
|
|
|
57
61
|
- "list_files": inspect the workspace or a subdirectory.
|
|
58
62
|
- "read_file": read a file, optionally with startLine/endLine.
|
|
59
63
|
- "search_code": search by text or regex-like pattern.
|
|
64
|
+
- "find_path": find files or directories by path/name when the user is looking for a folder, filename, or location.
|
|
60
65
|
- "run_shell": run a non-destructive command in the workspace.
|
|
61
66
|
- "apply_patch": update an existing file using one or more exact replacement hunks.
|
|
62
67
|
- "write_file": create a new file or fully rewrite a file when replacement is not practical.
|
|
@@ -80,6 +85,22 @@ function extractJson(text) {
|
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
function selectSupportedCodeProvider(config, availableProviders = getAvailableProviders(config || {})) {
|
|
89
|
+
const requestedProvider = (config && config.aiProvider) || 'gemini';
|
|
90
|
+
if (SUPPORTED_CODE_PROVIDERS.includes(requestedProvider) && availableProviders.includes(requestedProvider)) {
|
|
91
|
+
return requestedProvider;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const priority = ['anthropic', 'openai', 'gemini', 'local_openai'];
|
|
95
|
+
for (const provider of priority) {
|
|
96
|
+
if (availableProviders.includes(provider)) {
|
|
97
|
+
return provider;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return 'gemini';
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
function resolveWorkspacePath(workspaceRoot, targetPath = '.') {
|
|
84
105
|
const resolved = path.resolve(workspaceRoot, targetPath);
|
|
85
106
|
const relative = path.relative(workspaceRoot, resolved);
|
|
@@ -159,6 +180,40 @@ async function searchCode(workspaceRoot, query) {
|
|
|
159
180
|
}
|
|
160
181
|
}
|
|
161
182
|
|
|
183
|
+
async function findPaths(workspaceRoot, query, type = 'any') {
|
|
184
|
+
if (!query || !query.trim()) {
|
|
185
|
+
throw new Error('Path search query is required.');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const normalizedType = ['file', 'dir', 'any'].includes(type) ? type : 'any';
|
|
189
|
+
const loweredQuery = query.trim().toLowerCase();
|
|
190
|
+
const results = [];
|
|
191
|
+
|
|
192
|
+
function visit(currentPath) {
|
|
193
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const absoluteEntryPath = path.join(currentPath, entry.name);
|
|
196
|
+
const relativeEntryPath = path.relative(workspaceRoot, absoluteEntryPath) || '.';
|
|
197
|
+
const entryType = entry.isDirectory() ? 'dir' : 'file';
|
|
198
|
+
const matchesType = normalizedType === 'any' || normalizedType === entryType;
|
|
199
|
+
const matchesQuery = entry.name.toLowerCase().includes(loweredQuery) || relativeEntryPath.toLowerCase().includes(loweredQuery);
|
|
200
|
+
|
|
201
|
+
if (matchesType && matchesQuery) {
|
|
202
|
+
results.push(`${entryType === 'dir' ? '[dir]' : '[file]'} ${relativeEntryPath}`);
|
|
203
|
+
if (results.length >= 200) return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (entry.isDirectory() && results.length < 200) {
|
|
207
|
+
visit(absoluteEntryPath);
|
|
208
|
+
if (results.length >= 200) return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
visit(workspaceRoot);
|
|
214
|
+
return results.length > 0 ? results.join('\n') : '(no matching paths)';
|
|
215
|
+
}
|
|
216
|
+
|
|
162
217
|
function assertSafeShell(command) {
|
|
163
218
|
const blockedPatterns = [
|
|
164
219
|
/\brm\s+-rf\b/,
|
|
@@ -244,16 +299,124 @@ function writeFile(workspaceRoot, targetPath, content) {
|
|
|
244
299
|
return `Wrote ${targetPath}`;
|
|
245
300
|
}
|
|
246
301
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
302
|
+
class UnifiedAgentClient {
|
|
303
|
+
constructor(provider, config) {
|
|
304
|
+
this.provider = SUPPORTED_CODE_PROVIDERS.includes(provider) ? provider : 'gemini';
|
|
305
|
+
this.config = config;
|
|
306
|
+
this.history = [];
|
|
307
|
+
this.systemInstruction = CODE_AGENT_PROMPT;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async sendMessage(observation) {
|
|
311
|
+
this.history.push({ role: 'user', content: observation });
|
|
312
|
+
|
|
313
|
+
let responseText = '';
|
|
314
|
+
if (this.provider === 'anthropic') {
|
|
315
|
+
responseText = await this._callAnthropic();
|
|
316
|
+
} else if (this.provider === 'openai' || this.provider === 'local_openai') {
|
|
317
|
+
responseText = await this._callOpenAI();
|
|
318
|
+
} else {
|
|
319
|
+
responseText = await this._callGemini();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.history.push({ role: 'assistant', content: responseText });
|
|
323
|
+
return responseText;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async _callAnthropic() {
|
|
327
|
+
const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
328
|
+
const messages = this.history.map(m => ({
|
|
329
|
+
role: m.role,
|
|
330
|
+
content: m.content
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
const response = await axios.post('https://api.anthropic.com/v1/messages', {
|
|
334
|
+
model: this.config.anthropicModel || 'claude-3-5-sonnet-latest',
|
|
335
|
+
max_tokens: 8192,
|
|
336
|
+
system: this.systemInstruction,
|
|
337
|
+
messages: messages
|
|
338
|
+
}, {
|
|
339
|
+
headers: {
|
|
340
|
+
'x-api-key': apiKey,
|
|
341
|
+
'anthropic-version': '2023-06-01',
|
|
342
|
+
'content-type': 'application/json'
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
return response.data.content[0].text;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async _callOpenAI() {
|
|
349
|
+
const isLocal = this.provider === 'local_openai';
|
|
350
|
+
const apiKey = isLocal ? 'not-needed' : (this.config.openaiApiKey || process.env.OPENAI_API_KEY);
|
|
351
|
+
const baseUrl = isLocal ? (this.config.localApiBaseUrl || 'http://localhost:1234/v1') : 'https://api.openai.com/v1';
|
|
352
|
+
const model = isLocal ? (this.config.localModelName || 'local-model') : (this.config.openaiModel || 'gpt-4o');
|
|
353
|
+
|
|
354
|
+
const messages = [
|
|
355
|
+
{ role: 'system', content: this.systemInstruction },
|
|
356
|
+
...this.history
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
360
|
+
model: model,
|
|
361
|
+
messages: messages,
|
|
362
|
+
response_format: isLocal ? undefined : { type: "json_object" }
|
|
363
|
+
}, {
|
|
364
|
+
headers: {
|
|
365
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
366
|
+
'Content-Type': 'application/json'
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return response.data.choices[0].message.content;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async _callGemini() {
|
|
373
|
+
const apiKey = this.config.apiKey || process.env.GEMINI_API_KEY;
|
|
374
|
+
const model = this.config.geminiModel || DEFAULT_GEMINI_MODEL;
|
|
375
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
376
|
+
|
|
377
|
+
// Convert history for Gemini
|
|
378
|
+
const geminiHistory = this.history.slice(0, -1).map(m => ({
|
|
379
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
380
|
+
parts: [{ text: m.content }]
|
|
381
|
+
}));
|
|
382
|
+
|
|
383
|
+
const lastMessage = this.history[this.history.length - 1].content;
|
|
384
|
+
|
|
385
|
+
const chat = ai.chats.create({
|
|
386
|
+
model,
|
|
387
|
+
config: {
|
|
388
|
+
systemInstruction: this.systemInstruction,
|
|
389
|
+
responseMimeType: 'application/json'
|
|
390
|
+
},
|
|
391
|
+
history: geminiHistory
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const response = await chat.sendMessage({ message: [{ text: lastMessage }] });
|
|
395
|
+
return typeof response.text === 'function' ? response.text() : response.text;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function getAgentDecision(client, observation, options = {}) {
|
|
400
|
+
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
401
|
+
const step = options.step || 0;
|
|
402
|
+
|
|
403
|
+
let rawText = await client.sendMessage(observation);
|
|
404
|
+
for (let attempt = 0; attempt <= MAX_JSON_REPAIR_ATTEMPTS; attempt++) {
|
|
405
|
+
try {
|
|
406
|
+
return extractJson(rawText);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (attempt === MAX_JSON_REPAIR_ATTEMPTS) {
|
|
409
|
+
throw new Error(`Agent returned invalid JSON after ${MAX_JSON_REPAIR_ATTEMPTS + 1} attempts: ${error.message}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
onProgress(`Step ${step}: invalid JSON response, requesting repair (${attempt + 1}/${MAX_JSON_REPAIR_ATTEMPTS})`);
|
|
413
|
+
rawText = await client.sendMessage([
|
|
414
|
+
'Your previous response was not valid JSON for Code Mode.',
|
|
415
|
+
'Reply again with valid JSON only, following the required schema exactly.',
|
|
416
|
+
`Previous response:\n${truncate(rawText, 4000)}`
|
|
417
|
+
].join('\n'));
|
|
418
|
+
}
|
|
252
419
|
}
|
|
253
|
-
return {
|
|
254
|
-
ai: new GoogleGenAI({ apiKey }),
|
|
255
|
-
model: (config.geminiModel || DEFAULT_GEMINI_MODEL).trim() || DEFAULT_GEMINI_MODEL
|
|
256
|
-
};
|
|
257
420
|
}
|
|
258
421
|
|
|
259
422
|
function detectPackageManager(workspaceRoot) {
|
|
@@ -298,12 +461,17 @@ async function getGitContext(workspaceRoot) {
|
|
|
298
461
|
return { isRepo: true, branch, status, diffSummary };
|
|
299
462
|
}
|
|
300
463
|
|
|
301
|
-
async function buildInitialObservation(task, workspaceRoot) {
|
|
464
|
+
async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
302
465
|
const session = readWorkspaceSession(workspaceRoot);
|
|
303
466
|
const gitContext = await getGitContext(workspaceRoot);
|
|
304
467
|
const testCommands = detectTestCommands(workspaceRoot);
|
|
305
468
|
|
|
469
|
+
const contextStr = history.length > 0
|
|
470
|
+
? `Recent Context:\n${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}\n`
|
|
471
|
+
: '';
|
|
472
|
+
|
|
306
473
|
return [
|
|
474
|
+
contextStr,
|
|
307
475
|
`Task: ${task}`,
|
|
308
476
|
`Workspace: ${workspaceRoot}`,
|
|
309
477
|
`Git branch: ${gitContext.branch}`,
|
|
@@ -323,45 +491,41 @@ async function buildInitialObservation(task, workspaceRoot) {
|
|
|
323
491
|
|
|
324
492
|
async function executeCodeTask(task, options = {}) {
|
|
325
493
|
const workspaceRoot = path.resolve(options.cwd || process.cwd());
|
|
494
|
+
const history = options.history || [];
|
|
326
495
|
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
327
496
|
const requestApproval = typeof options.requestApproval === 'function'
|
|
328
497
|
? options.requestApproval
|
|
329
498
|
: async () => true;
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
model,
|
|
334
|
-
config: {
|
|
335
|
-
systemInstruction: CODE_AGENT_PROMPT,
|
|
336
|
-
responseMimeType: 'application/json'
|
|
337
|
-
},
|
|
338
|
-
history: []
|
|
339
|
-
});
|
|
499
|
+
const config = readConfig();
|
|
500
|
+
const provider = options.provider || selectSupportedCodeProvider(config);
|
|
501
|
+
const client = new UnifiedAgentClient(provider, config);
|
|
340
502
|
|
|
341
|
-
let observation = await buildInitialObservation(task, workspaceRoot);
|
|
503
|
+
let observation = await buildInitialObservation(task, workspaceRoot, history);
|
|
504
|
+
|
|
505
|
+
let finalSummary = '';
|
|
506
|
+
let finalVerification = '';
|
|
507
|
+
let finalSessionSummary = '';
|
|
508
|
+
let executedSteps = 0;
|
|
342
509
|
|
|
343
510
|
for (let step = 1; step <= MAX_AGENT_STEPS; step++) {
|
|
511
|
+
executedSteps = step;
|
|
344
512
|
onProgress(`Step ${step}: thinking`);
|
|
345
|
-
const
|
|
346
|
-
const text = typeof response.text === 'function' ? response.text() : response.text;
|
|
347
|
-
const decision = extractJson(text);
|
|
513
|
+
const decision = await getAgentDecision(client, observation, { onProgress, step });
|
|
348
514
|
const action = decision.action;
|
|
349
515
|
const input = decision.input || {};
|
|
350
516
|
|
|
351
517
|
onProgress(`Step ${step}: ${action}${input.path ? ` ${input.path}` : input.command ? ` ${input.command}` : ''}`);
|
|
352
518
|
|
|
353
519
|
if (action === 'finish') {
|
|
354
|
-
|
|
520
|
+
finalSessionSummary = input.sessionSummary || input.summary || task;
|
|
521
|
+
finalSummary = input.summary || 'Task complete.';
|
|
522
|
+
finalVerification = input.verification || 'Not specified.';
|
|
355
523
|
writeWorkspaceSession(workspaceRoot, {
|
|
356
|
-
summary:
|
|
524
|
+
summary: finalSessionSummary,
|
|
357
525
|
lastTask: task,
|
|
358
|
-
lastVerification:
|
|
526
|
+
lastVerification: finalVerification
|
|
359
527
|
});
|
|
360
|
-
|
|
361
|
-
summary: input.summary || 'Task complete.',
|
|
362
|
-
verification: input.verification || 'Not specified.',
|
|
363
|
-
steps: step
|
|
364
|
-
};
|
|
528
|
+
break;
|
|
365
529
|
}
|
|
366
530
|
|
|
367
531
|
let toolResult = '';
|
|
@@ -375,6 +539,9 @@ async function executeCodeTask(task, options = {}) {
|
|
|
375
539
|
case 'search_code':
|
|
376
540
|
toolResult = await searchCode(workspaceRoot, input.query);
|
|
377
541
|
break;
|
|
542
|
+
case 'find_path':
|
|
543
|
+
toolResult = await findPaths(workspaceRoot, input.query, input.type);
|
|
544
|
+
break;
|
|
378
545
|
case 'run_shell': {
|
|
379
546
|
const approved = await requestApproval({
|
|
380
547
|
type: 'shell',
|
|
@@ -427,6 +594,45 @@ async function executeCodeTask(task, options = {}) {
|
|
|
427
594
|
].join('\n');
|
|
428
595
|
}
|
|
429
596
|
|
|
597
|
+
// Check for Agent Collaboration (Review)
|
|
598
|
+
if (config.enableAgentCollaboration !== false) {
|
|
599
|
+
const availableProviders = getAvailableProviders(config);
|
|
600
|
+
// Exclude providers that often need special local setup or are slow/unreliable for tiny reviews
|
|
601
|
+
const altProviders = availableProviders.filter(p => p !== provider && p !== 'ollama' && p !== 'huggingface' && p !== 'local_openai');
|
|
602
|
+
|
|
603
|
+
// Fallback to provider itself if no other good ones exist, or pick the best available
|
|
604
|
+
const reviewerProvider = altProviders.length > 0
|
|
605
|
+
? altProviders[0]
|
|
606
|
+
: (availableProviders.includes('gemini') ? 'gemini' : availableProviders[0]);
|
|
607
|
+
|
|
608
|
+
if (reviewerProvider && finalSummary) {
|
|
609
|
+
onProgress(`Invoking Reviewer Agent (${reviewerProvider})...`);
|
|
610
|
+
|
|
611
|
+
const reviewerClient = new UnifiedAgentClient(reviewerProvider, config);
|
|
612
|
+
reviewerClient.systemInstruction = CODE_AGENT_PROMPT + "\n\nYou are the Reviewer Agent. Review the primary agent's changes, test output, and verification. If you spot a critical bug, point it out. Otherwise, confirm it looks good. Return JSON with action: 'finish' and your review in the 'summary' field.";
|
|
613
|
+
|
|
614
|
+
const reviewPrompt = `The primary agent (${provider}) just completed the task: "${task}".\nSummary: ${finalSummary}\nVerification: ${finalVerification}\nGit Status: ${(await getGitContext(workspaceRoot)).status}\n\nPlease review this. Return JSON with action: 'finish'.`;
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
const reviewResponse = await reviewerClient.sendMessage(reviewPrompt);
|
|
618
|
+
const reviewDecision = extractJson(reviewResponse);
|
|
619
|
+
const reviewInput = reviewDecision.input || {};
|
|
620
|
+
|
|
621
|
+
finalSummary += `\n\n[Review by ${reviewerProvider}]\n${reviewInput.summary || reviewDecision.thought || 'Looks good.'}`;
|
|
622
|
+
} catch (e) {
|
|
623
|
+
onProgress(`Reviewer Agent failed: ${e.message}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (finalSummary) {
|
|
629
|
+
return {
|
|
630
|
+
summary: finalSummary,
|
|
631
|
+
verification: finalVerification,
|
|
632
|
+
steps: executedSteps
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
430
636
|
writeWorkspaceSession(workspaceRoot, {
|
|
431
637
|
summary: `Task stopped before completion: ${task}`,
|
|
432
638
|
lastTask: task,
|
|
@@ -436,8 +642,15 @@ async function executeCodeTask(task, options = {}) {
|
|
|
436
642
|
return {
|
|
437
643
|
summary: 'Stopped after reaching the maximum number of agent steps.',
|
|
438
644
|
verification: 'Agent limit reached before explicit completion.',
|
|
439
|
-
steps: MAX_AGENT_STEPS
|
|
645
|
+
steps: executedSteps || MAX_AGENT_STEPS
|
|
440
646
|
};
|
|
441
647
|
}
|
|
442
648
|
|
|
443
|
-
module.exports = {
|
|
649
|
+
module.exports = {
|
|
650
|
+
executeCodeTask,
|
|
651
|
+
_helpers: {
|
|
652
|
+
extractJson,
|
|
653
|
+
selectSupportedCodeProvider,
|
|
654
|
+
findPaths
|
|
655
|
+
}
|
|
656
|
+
};
|
package/src/CLI/onboarding.js
CHANGED
|
@@ -18,28 +18,75 @@ async function runOnboarding(options = {}) {
|
|
|
18
18
|
{
|
|
19
19
|
type: 'input',
|
|
20
20
|
name: 'apiKey',
|
|
21
|
-
message: '
|
|
21
|
+
message: 'Enter your Google Gemini API Key (Required for basic features):',
|
|
22
22
|
default: config.apiKey || undefined,
|
|
23
|
-
validate: (input) => input.length > 0 ? true : 'API Key is required.'
|
|
23
|
+
validate: (input) => input.trim().length > 0 ? true : 'API Key is required.'
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
type: 'list',
|
|
27
|
-
name: '
|
|
28
|
-
message: 'Select the Gemini model to use:',
|
|
27
|
+
name: 'geminiModelChoice',
|
|
28
|
+
message: 'Select the primary Gemini model to use:',
|
|
29
29
|
choices: [
|
|
30
30
|
'gemini-2.5-flash',
|
|
31
|
+
'gemini-2.0-pro-exp-02-05',
|
|
31
32
|
'gemini-3.1-flash-lite-preview',
|
|
32
33
|
'gemini-3.1-flash-lite',
|
|
33
|
-
'
|
|
34
|
+
'Custom model name'
|
|
34
35
|
],
|
|
35
36
|
default: config.geminiModel || 'gemini-2.5-flash'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'input',
|
|
40
|
+
name: 'customGeminiModel',
|
|
41
|
+
message: 'Enter your custom Gemini model name:',
|
|
42
|
+
when: (answers) => answers.geminiModelChoice === 'Custom model name',
|
|
43
|
+
validate: (input) => input.trim().length > 0 ? true : 'Please enter a valid model name.'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'anthropicApiKey',
|
|
48
|
+
message: 'Enter your Anthropic API Key (Optional, press Enter to skip):',
|
|
49
|
+
default: config.anthropicApiKey || ''
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'openaiApiKey',
|
|
54
|
+
message: 'Enter your OpenAI API Key (Optional, press Enter to skip):',
|
|
55
|
+
default: config.openaiApiKey || ''
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'input',
|
|
59
|
+
name: 'hfApiKey',
|
|
60
|
+
message: 'Enter your Hugging Face API Key (Optional, press Enter to skip):',
|
|
61
|
+
default: config.hfApiKey || ''
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: 'input',
|
|
65
|
+
name: 'localApiBaseUrl',
|
|
66
|
+
message: 'Enter your Local AI (LM Studio/OpenAI Compatible) Base URL (Optional, press Enter to skip):',
|
|
67
|
+
default: config.localApiBaseUrl || ''
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'input',
|
|
71
|
+
name: 'localModelName',
|
|
72
|
+
message: 'Enter your Local Model Name (Optional, press Enter to skip):',
|
|
73
|
+
default: config.localModelName || ''
|
|
36
74
|
}
|
|
37
75
|
];
|
|
38
76
|
|
|
39
77
|
const answers = await inquirer.prompt(questions);
|
|
78
|
+
|
|
79
|
+
// Resolve custom gemini model if selected
|
|
80
|
+
const geminiModel = answers.geminiModelChoice === 'Custom model name'
|
|
81
|
+
? answers.customGeminiModel
|
|
82
|
+
: answers.geminiModelChoice;
|
|
83
|
+
|
|
84
|
+
// Remove temporary choice fields before saving
|
|
85
|
+
delete answers.geminiModelChoice;
|
|
86
|
+
delete answers.customGeminiModel;
|
|
40
87
|
|
|
41
88
|
// Save configuration
|
|
42
|
-
const newConfig = { ...config, ...answers };
|
|
89
|
+
const newConfig = { ...config, ...answers, geminiModel };
|
|
43
90
|
writeConfig(newConfig);
|
|
44
91
|
console.log('\n✅ Configuration saved successfully!');
|
|
45
92
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mint Workspace Manager
|
|
3
|
+
* -----------------------
|
|
4
|
+
* Manages project-specific contexts and persistent workspaces.
|
|
5
|
+
* Stores data in ~/.config/mint/workspaces.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
function getWorkspaceFile() {
|
|
13
|
+
return process.env.MINT_WORKSPACE_FILE || path.join(os.homedir(), '.config', 'mint', 'workspaces.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ensureDir() {
|
|
17
|
+
const dir = path.dirname(getWorkspaceFile());
|
|
18
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadWorkspaces() {
|
|
22
|
+
const workspaceFile = getWorkspaceFile();
|
|
23
|
+
ensureDir();
|
|
24
|
+
if (!fs.existsSync(workspaceFile)) return {};
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(workspaceFile, 'utf8'));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveWorkspaces(data) {
|
|
33
|
+
const workspaceFile = getWorkspaceFile();
|
|
34
|
+
ensureDir();
|
|
35
|
+
fs.writeFileSync(workspaceFile, JSON.stringify(data, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPathInsideWorkspace(currentPath, workspacePath) {
|
|
39
|
+
const relative = path.relative(workspacePath, currentPath);
|
|
40
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function addWorkspace(name, rootPath, instructions = '') {
|
|
44
|
+
const workspaces = loadWorkspaces();
|
|
45
|
+
const absolutePath = path.resolve(rootPath);
|
|
46
|
+
workspaces[name] = {
|
|
47
|
+
name,
|
|
48
|
+
path: absolutePath,
|
|
49
|
+
instructions,
|
|
50
|
+
addedAt: new Date().toISOString(),
|
|
51
|
+
lastAccessed: new Date().toISOString()
|
|
52
|
+
};
|
|
53
|
+
saveWorkspaces(workspaces);
|
|
54
|
+
return workspaces[name];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function removeWorkspace(name) {
|
|
58
|
+
const workspaces = loadWorkspaces();
|
|
59
|
+
if (workspaces[name]) {
|
|
60
|
+
delete workspaces[name];
|
|
61
|
+
saveWorkspaces(workspaces);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getWorkspaceByPath(currentPath) {
|
|
68
|
+
const workspaces = loadWorkspaces();
|
|
69
|
+
const absoluteCurrent = path.resolve(currentPath);
|
|
70
|
+
|
|
71
|
+
// Find workspace where current path is inside or equal to workspace path
|
|
72
|
+
for (const name in workspaces) {
|
|
73
|
+
const ws = workspaces[name];
|
|
74
|
+
if (isPathInsideWorkspace(absoluteCurrent, ws.path)) {
|
|
75
|
+
return ws;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function listWorkspaces() {
|
|
82
|
+
return loadWorkspaces();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
addWorkspace,
|
|
87
|
+
removeWorkspace,
|
|
88
|
+
getWorkspaceByPath,
|
|
89
|
+
listWorkspaces
|
|
90
|
+
};
|
package/src/Plugins/docker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execFile } = require('child_process');
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
name: 'docker',
|
|
@@ -8,28 +8,30 @@ module.exports = {
|
|
|
8
8
|
return new Promise((resolve) => {
|
|
9
9
|
console.log(`[Docker Plugin] Executing command: ${target}`);
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const rawTarget = (target || '').trim();
|
|
12
|
+
const [rawAction, ...args] = rawTarget.split(/\s+/);
|
|
13
|
+
const action = (rawAction || '').toLowerCase();
|
|
12
14
|
const containerName = args.join(' ');
|
|
13
|
-
|
|
14
|
-
let cmd = '';
|
|
15
|
+
let commandArgs = [];
|
|
15
16
|
|
|
16
17
|
if (action === 'list') {
|
|
17
|
-
|
|
18
|
+
commandArgs = ['ps', '--format', '{{.Names}} ({{.Status}})'];
|
|
18
19
|
} else if (['start', 'stop', 'restart'].includes(action) && containerName) {
|
|
19
|
-
|
|
20
|
+
commandArgs = [action, containerName];
|
|
20
21
|
} else {
|
|
21
22
|
return resolve(`Invalid docker command or missing container name: ${target}`);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
execFile('docker', commandArgs, (error, stdout, stderr) => {
|
|
25
26
|
if (error) {
|
|
26
|
-
|
|
27
|
+
const stderrText = stderr || '';
|
|
28
|
+
if (error.code === 127 || stderrText.includes('not found') || error.code === 'ENOENT') {
|
|
27
29
|
return resolve('Error: Docker is not installed or not in PATH.');
|
|
28
30
|
}
|
|
29
|
-
if (
|
|
31
|
+
if (stderrText.toLowerCase().includes('permission denied')) {
|
|
30
32
|
return resolve('Error: Permission denied. You might need to add your user to the "docker" group.');
|
|
31
33
|
}
|
|
32
|
-
return resolve(`Docker Error: ${
|
|
34
|
+
return resolve(`Docker Error: ${stderrText || error.message}`);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
if (action === 'list') {
|