@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.
@@ -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
- // ถ้าไม่ใช่ absolute path ให้สร้างบน Desktop
23
- if (!path.isAbsolute(target)) {
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, target);
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(target);
45
- if (result) console.error('openFile error:', 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
- exec(`xdg-open "${target}"`, (err) => {
48
- if (err) console.error("Failed to open path via xdg-open:", err);
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(target);
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 "${target}"`, (err) => {
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
+ };