@pheem49/mint 1.2.3 → 1.3.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 +28 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/index.html +53 -5
- package/docs/style.css +295 -6
- package/index.html +16 -0
- package/main.js +36 -83
- package/mint-cli-logic.js +19 -0
- package/mint-cli.js +121 -17
- package/package.json +8 -2
- package/src/AI_Brain/Gemini_API.js +175 -9
- package/src/AI_Brain/knowledge_base.js +199 -125
- package/src/Automation_Layer/file_operations.js +74 -10
- package/src/CLI/chat_router.js +166 -0
- package/src/CLI/chat_ui.js +239 -110
- package/src/CLI/code_agent.js +443 -0
- package/src/CLI/code_session_memory.js +62 -0
- package/src/CLI/list_features.js +1 -0
- package/src/Plugins/mcp_manager.js +95 -0
- package/src/Plugins/plugin_manager.js +2 -2
- package/src/System/config_manager.js +27 -7
- package/src/System/granular_automation.js +88 -0
- package/src/UI/settings.html +24 -0
- package/src/UI/settings.js +98 -1
- package/docs/assets/hero-bg.png +0 -0
- package/docs/assets/logo.png +0 -0
|
@@ -9,6 +9,50 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Smartly resolves a path.
|
|
14
|
+
* If a path starts with '/' but doesn't exist at root, checks if it exists relative to home.
|
|
15
|
+
* Also handles '~/' expansion.
|
|
16
|
+
*/
|
|
17
|
+
function resolveSmartPath(target) {
|
|
18
|
+
if (!target) return target;
|
|
19
|
+
|
|
20
|
+
const home = os.homedir();
|
|
21
|
+
const commonFolders = ['Downloads', 'Desktop', 'Documents', 'Videos', 'Pictures', 'Music', 'vscode', 'Games'];
|
|
22
|
+
|
|
23
|
+
// 1. If it's already an absolute path and exists, use it
|
|
24
|
+
if (path.isAbsolute(target) && fs.existsSync(target)) return target;
|
|
25
|
+
|
|
26
|
+
// 2. If it starts with ~/ expand it
|
|
27
|
+
if (target.startsWith('~/')) {
|
|
28
|
+
const expanded = path.join(home, target.substring(2));
|
|
29
|
+
if (fs.existsSync(expanded)) return expanded;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 3. If it starts with / but doesn't exist at root, try home directory
|
|
33
|
+
if (target.startsWith('/')) {
|
|
34
|
+
const homeRelative = path.join(home, target.substring(1));
|
|
35
|
+
if (fs.existsSync(homeRelative)) return homeRelative;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 4. Check if the target itself starts with a common folder (e.g., "Downloads/resume.pdf")
|
|
39
|
+
const parts = target.split(/[/\\]/);
|
|
40
|
+
const firstPart = parts[0];
|
|
41
|
+
if (commonFolders.includes(firstPart)) {
|
|
42
|
+
const potentialPath = path.join(home, target);
|
|
43
|
+
if (fs.existsSync(potentialPath)) return potentialPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 5. Try searching the filename in all common folders
|
|
47
|
+
for (const folder of commonFolders) {
|
|
48
|
+
const potentialPath = path.join(home, folder, target);
|
|
49
|
+
if (fs.existsSync(potentialPath)) return potentialPath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 6. Final fallback: just return as is (might be relative to CWD)
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
|
|
12
56
|
/**
|
|
13
57
|
* สร้างโฟลเดอร์ใหม่
|
|
14
58
|
* target: ชื่อโฟลเดอร์ หรือ absolute path
|
|
@@ -17,12 +61,12 @@ const os = require('os');
|
|
|
17
61
|
function createFolder(target) {
|
|
18
62
|
if (!target) return { success: false, message: 'No folder name provided.' };
|
|
19
63
|
|
|
20
|
-
let folderPath = target;
|
|
64
|
+
let folderPath = resolveSmartPath(target);
|
|
21
65
|
|
|
22
|
-
//
|
|
23
|
-
if (!path.isAbsolute(
|
|
66
|
+
// If still not absolute (was just a name), default to Desktop
|
|
67
|
+
if (!path.isAbsolute(folderPath)) {
|
|
24
68
|
const desktopPath = path.join(os.homedir(), 'Desktop');
|
|
25
|
-
folderPath = path.join(desktopPath,
|
|
69
|
+
folderPath = path.join(desktopPath, folderPath);
|
|
26
70
|
}
|
|
27
71
|
|
|
28
72
|
try {
|
|
@@ -40,24 +84,44 @@ function createFolder(target) {
|
|
|
40
84
|
*/
|
|
41
85
|
async function openFile(target) {
|
|
42
86
|
if (!target) return;
|
|
87
|
+
const resolvedPath = resolveSmartPath(target);
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
90
|
+
console.error(`[OpenFile] File not found: ${resolvedPath}`);
|
|
91
|
+
return `ไม่พบไฟล์หรือโฟลเดอร์: ${target} ค่ะ`;
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
if (shell) {
|
|
44
|
-
const result = await shell.openPath(
|
|
45
|
-
if (result)
|
|
95
|
+
const result = await shell.openPath(resolvedPath);
|
|
96
|
+
if (result) {
|
|
97
|
+
console.error('openFile error:', result);
|
|
98
|
+
return `เกิดข้อผิดพลาดในการเปิดไฟล์: ${result}`;
|
|
99
|
+
}
|
|
46
100
|
} else {
|
|
47
|
-
|
|
48
|
-
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
exec(`xdg-open "${resolvedPath}"`, (err) => {
|
|
103
|
+
if (err) {
|
|
104
|
+
console.error("Failed to open path via xdg-open:", err);
|
|
105
|
+
resolve(`ไม่สามารถเปิดไฟล์ได้ค่ะ: ${err.message}`);
|
|
106
|
+
} else {
|
|
107
|
+
resolve(true);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
49
110
|
});
|
|
50
111
|
}
|
|
51
112
|
}
|
|
52
113
|
|
|
114
|
+
|
|
53
115
|
/**
|
|
54
116
|
* ลบไฟล์หรือโฟลเดอร์ (ย้ายไป Trash)
|
|
55
117
|
*/
|
|
56
118
|
async function deleteFile(target) {
|
|
57
119
|
if (!target) return { success: false, message: 'No path provided.' };
|
|
120
|
+
const resolvedPath = resolveSmartPath(target);
|
|
121
|
+
|
|
58
122
|
if (shell) {
|
|
59
123
|
try {
|
|
60
|
-
await shell.trashItem(
|
|
124
|
+
await shell.trashItem(resolvedPath);
|
|
61
125
|
return { success: true };
|
|
62
126
|
} catch (err) {
|
|
63
127
|
console.error('deleteFile error:', err);
|
|
@@ -65,7 +129,7 @@ async function deleteFile(target) {
|
|
|
65
129
|
}
|
|
66
130
|
} else {
|
|
67
131
|
return new Promise((resolve) => {
|
|
68
|
-
exec(`gio trash "${
|
|
132
|
+
exec(`gio trash "${resolvedPath}"`, (err) => {
|
|
69
133
|
if (err) {
|
|
70
134
|
console.error("Failed to trash item via gio trash:", err);
|
|
71
135
|
resolve({ success: false, message: err.message });
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
4
|
+
const { executeCodeTask } = require('./code_agent');
|
|
5
|
+
const { readConfig } = require('../System/config_manager');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
8
|
+
|
|
9
|
+
const CODE_KEYWORDS = [
|
|
10
|
+
'code', 'repo', 'repository', 'project', 'workspace', 'file', 'files', 'readme',
|
|
11
|
+
'package.json', 'bug', 'fix', 'refactor', 'test', 'tests', 'build', 'lint',
|
|
12
|
+
'implement', 'feature', 'cli', 'function', 'module', 'component', 'diff'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const THAI_CODE_KEYWORDS = [
|
|
16
|
+
'โค้ด', 'โปรเจค', 'โปรเจ็กต์', 'ไฟล์', 'รีโป', 'บั๊ก', 'แก้', 'ทดสอบ', 'เทสต์',
|
|
17
|
+
'รีแฟกเตอร์', 'ฟีเจอร์', 'คอมโพเนนต์', 'ฟังก์ชัน', 'อ่าน', 'สำรวจ', 'โครงสร้าง'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const ROUTER_PROMPT = `You classify whether a chat message should be routed to a coding agent for the current local workspace.
|
|
21
|
+
|
|
22
|
+
Return JSON only:
|
|
23
|
+
{
|
|
24
|
+
"route": "code" | "chat",
|
|
25
|
+
"reason": "short explanation"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Choose "code" when the user is asking to inspect, edit, review, debug, explain, refactor, verify, or otherwise operate on the current project/workspace/codebase/files.
|
|
29
|
+
Choose "chat" for general conversation, factual Q&A, or non-code assistant tasks.`;
|
|
30
|
+
|
|
31
|
+
function workspaceLooksLikeCodebase(workspaceRoot) {
|
|
32
|
+
const markers = [
|
|
33
|
+
'package.json',
|
|
34
|
+
'.git',
|
|
35
|
+
'src',
|
|
36
|
+
'README.md',
|
|
37
|
+
'tsconfig.json',
|
|
38
|
+
'Cargo.toml',
|
|
39
|
+
'pyproject.toml'
|
|
40
|
+
];
|
|
41
|
+
return markers.some(marker => fs.existsSync(path.join(workspaceRoot, marker)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function detectCodeIntentHeuristic(text, workspaceRoot = process.cwd()) {
|
|
45
|
+
const input = (text || '').trim().toLowerCase();
|
|
46
|
+
if (!input) return false;
|
|
47
|
+
if (input.startsWith('/code ')) return true;
|
|
48
|
+
|
|
49
|
+
const hasCodeKeyword = CODE_KEYWORDS.some(keyword => input.includes(keyword));
|
|
50
|
+
const hasThaiCodeKeyword = THAI_CODE_KEYWORDS.some(keyword => input.includes(keyword));
|
|
51
|
+
const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace/.test(input);
|
|
52
|
+
const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|implement|inspect|explore|fix|update|change|refactor|review|explain|debug/.test(input);
|
|
53
|
+
|
|
54
|
+
return workspaceLooksLikeCodebase(workspaceRoot) && (referencesProject || ((hasCodeKeyword || hasThaiCodeKeyword) && asksForAction));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getRouterClient() {
|
|
58
|
+
const config = readConfig();
|
|
59
|
+
const apiKey = (config.apiKey || process.env.GEMINI_API_KEY || '').trim();
|
|
60
|
+
if (!apiKey) return null;
|
|
61
|
+
return {
|
|
62
|
+
ai: new GoogleGenAI({ apiKey }),
|
|
63
|
+
model: (config.geminiModel || DEFAULT_GEMINI_MODEL).trim() || DEFAULT_GEMINI_MODEL
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function summarizeWorkspace(workspaceRoot) {
|
|
68
|
+
const candidates = ['package.json', 'README.md', 'src', '.git'];
|
|
69
|
+
return candidates
|
|
70
|
+
.filter(item => fs.existsSync(path.join(workspaceRoot, item)))
|
|
71
|
+
.join(', ') || '(no obvious code markers)';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
75
|
+
const input = (text || '').trim();
|
|
76
|
+
if (!input) {
|
|
77
|
+
return { route: 'chat', reason: 'Empty input.' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (input.startsWith('/code ')) {
|
|
81
|
+
return { route: 'code', reason: 'Explicit /code command.' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const heuristicRoute = detectCodeIntentHeuristic(input, workspaceRoot);
|
|
85
|
+
const routerClient = getRouterClient();
|
|
86
|
+
if (!routerClient) {
|
|
87
|
+
return {
|
|
88
|
+
route: heuristicRoute ? 'code' : 'chat',
|
|
89
|
+
reason: heuristicRoute ? 'Heuristic code intent match.' : 'Heuristic chat fallback.'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const response = await routerClient.ai.models.generateContent({
|
|
95
|
+
model: routerClient.model,
|
|
96
|
+
config: {
|
|
97
|
+
systemInstruction: ROUTER_PROMPT,
|
|
98
|
+
responseMimeType: 'application/json'
|
|
99
|
+
},
|
|
100
|
+
contents: [{
|
|
101
|
+
role: 'user',
|
|
102
|
+
parts: [{
|
|
103
|
+
text: [
|
|
104
|
+
`Workspace: ${workspaceRoot}`,
|
|
105
|
+
`Workspace markers: ${summarizeWorkspace(workspaceRoot)}`,
|
|
106
|
+
`Message: ${input}`
|
|
107
|
+
].join('\n')
|
|
108
|
+
}]
|
|
109
|
+
}]
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const textOutput = typeof response.text === 'function' ? response.text() : response.text;
|
|
113
|
+
const parsed = JSON.parse(textOutput);
|
|
114
|
+
const route = parsed.route === 'code' ? 'code' : 'chat';
|
|
115
|
+
return {
|
|
116
|
+
route,
|
|
117
|
+
reason: parsed.reason || (route === 'code' ? 'Model classified as code.' : 'Model classified as chat.')
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
route: heuristicRoute ? 'code' : 'chat',
|
|
122
|
+
reason: heuristicRoute ? 'Heuristic fallback after router error.' : 'Chat fallback after router error.'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function runChatRoutedTask(input, context) {
|
|
128
|
+
const text = input.startsWith('/code ') ? input.slice('/code '.length).trim() : input;
|
|
129
|
+
const { appendMessage, setThinking, requestApproval, setMode } = context;
|
|
130
|
+
|
|
131
|
+
appendMessage('system', `Routing this request to Code Mode for workspace: ${process.cwd()}`);
|
|
132
|
+
if (setMode) setMode('Code');
|
|
133
|
+
|
|
134
|
+
let seconds = 0;
|
|
135
|
+
setThinking(true, seconds);
|
|
136
|
+
const timer = setInterval(() => {
|
|
137
|
+
seconds++;
|
|
138
|
+
setThinking(true, seconds);
|
|
139
|
+
}, 1000);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const result = await executeCodeTask(text, {
|
|
143
|
+
cwd: process.cwd(),
|
|
144
|
+
requestApproval,
|
|
145
|
+
onProgress: (message) => appendMessage('system', `[Code] ${message}`)
|
|
146
|
+
});
|
|
147
|
+
clearInterval(timer);
|
|
148
|
+
setThinking(false);
|
|
149
|
+
appendMessage('assistant', [
|
|
150
|
+
`Code Mode finished.`,
|
|
151
|
+
result.summary,
|
|
152
|
+
`Verification: ${result.verification}`
|
|
153
|
+
].join('\n'));
|
|
154
|
+
} catch (error) {
|
|
155
|
+
clearInterval(timer);
|
|
156
|
+
setThinking(false);
|
|
157
|
+
appendMessage('error', error.message);
|
|
158
|
+
} finally {
|
|
159
|
+
if (setMode) setMode('Chat');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
detectCodeIntent,
|
|
165
|
+
runChatRoutedTask
|
|
166
|
+
};
|