@pheem49/mint 1.5.3 → 1.5.5

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/GUIDE_TH.md CHANGED
@@ -6,15 +6,27 @@
6
6
 
7
7
  ## 1. การติดตั้ง (Installation)
8
8
 
9
- คุณสามารถติดตั้ง Mint ได้สองวิธีหลักๆ ดังนี้:
9
+ คุณสามารถติดตั้ง Mint ได้ด้วยวิธีต่างๆ ดังนี้:
10
10
 
11
- ### วิธีที่ 1: ติดตั้งผ่าน NPM (แนะนำสำหรับผู้ใช้ทั่วไป)
12
- หากคุณต้องการใช้งานคำสั่ง `mint` ได้จากทุกที่ในเครื่อง:
11
+ ### วิธีที่ 1: ติดตั้งแบบด่วน (แนะนำ)
12
+ วิธีนี้จะติดตั้ง Mint CLI ให้อัตโนมัติในคำสั่งเดียว:
13
+ **สำหรับ macOS และ Linux:**
14
+ ```bash
15
+ curl -fsSL https://raw.githubusercontent.com/Pheem49/Mint/main/install.sh | bash
16
+ ```
17
+
18
+ **สำหรับ Windows (PowerShell หรือ CMD):**
19
+ ```powershell
20
+ powershell -Command "iwr -useb https://raw.githubusercontent.com/Pheem49/Mint/main/install.ps1 | iex"
21
+ ```
22
+
23
+ ### วิธีที่ 2: ติดตั้งผ่าน NPM
24
+ หากคุณต้องการใช้งานคำสั่ง `mint` และถนัดการใช้ NPM:
13
25
  ```bash
14
26
  npm install -g @pheem49/mint@latest
15
27
  ```
16
28
 
17
- ### วิธีที่ 2: ติดตั้งจาก Source Code (สำหรับนักพัฒนา)
29
+ ### วิธีที่ 3: ติดตั้งจาก Source Code (สำหรับนักพัฒนา)
18
30
  หากคุณต้องการแก้ไขโค้ดหรือลองฟีเจอร์ใหม่ๆ:
19
31
  1. Clone repository: `git clone https://github.com/Pheem49/Mint.git`
20
32
  2. เข้าไปที่โฟลเดอร์: `cd Mint`
package/README.md CHANGED
@@ -39,7 +39,23 @@ Mint is an AI assistant built to live in your desktop and terminal. It combines
39
39
 
40
40
  ## Installation & Setup
41
41
 
42
- ### Global Install
42
+ ### Quick Install (Recommended)
43
+
44
+ The easiest way to install Mint CLI is using our installation script:
45
+
46
+ **For macOS & Linux:**
47
+ ```bash
48
+ curl -fsSL https://raw.githubusercontent.com/Pheem49/Mint/main/install.sh | bash
49
+ ```
50
+
51
+ **For Windows (PowerShell or CMD):**
52
+ ```powershell
53
+ powershell -Command "iwr -useb https://raw.githubusercontent.com/Pheem49/Mint/main/install.ps1 | iex"
54
+ ```
55
+
56
+ ### NPM Install
57
+
58
+ If you prefer to install via NPM directly:
43
59
 
44
60
  ```bash
45
61
  npm install -g @pheem49/mint@latest
package/install.ps1 ADDED
@@ -0,0 +1,64 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Mint CLI Installation Script for Windows
4
+ .DESCRIPTION
5
+ Installs the Mint CLI package globally via NPM.
6
+ #>
7
+
8
+ $ErrorActionPreference = "Stop"
9
+
10
+ Write-Host "Starting Mint CLI installation..." -ForegroundColor Cyan
11
+
12
+ # 1. Check for Node.js
13
+ try {
14
+ $nodeVersion = (node -v) 2>$null
15
+ if ([string]::IsNullOrWhiteSpace($nodeVersion)) {
16
+ Write-Host "[Error] Node.js is not found on your system!" -ForegroundColor Red
17
+ Write-Host "Please install Node.js (version 18 or higher) before using Mint CLI." -ForegroundColor Yellow
18
+ Write-Host "Download at: https://nodejs.org/"
19
+ exit 1
20
+ }
21
+ } catch {
22
+ Write-Host "[Error] Node.js is not found on your system!" -ForegroundColor Red
23
+ Write-Host "Please install Node.js (version 18 or higher) before using Mint CLI." -ForegroundColor Yellow
24
+ Write-Host "Download at: https://nodejs.org/"
25
+ exit 1
26
+ }
27
+
28
+ # 2. Check for NPM
29
+ try {
30
+ $npmVersion = (npm -v) 2>$null
31
+ if ([string]::IsNullOrWhiteSpace($npmVersion)) {
32
+ Write-Host "[Error] NPM is not found on your system!" -ForegroundColor Red
33
+ exit 1
34
+ }
35
+ } catch {
36
+ Write-Host "[Error] NPM is not found on your system!" -ForegroundColor Red
37
+ exit 1
38
+ }
39
+
40
+ # Check Node.js version (recommend 18+)
41
+ $majorVersion = [int]($nodeVersion.Substring(1).Split('.')[0])
42
+ if ($majorVersion -lt 18) {
43
+ Write-Host "[Warning] Your Node.js version is too old (current: $nodeVersion)" -ForegroundColor Yellow
44
+ Write-Host "It is recommended to update to version 18 or higher for full functionality." -ForegroundColor Yellow
45
+ }
46
+
47
+ # 3. Install Mint CLI via NPM
48
+ Write-Host "Downloading and installing @pheem49/mint..." -ForegroundColor Cyan
49
+
50
+ try {
51
+ # In Windows, we usually don't need sudo for global npm install if Node was installed for the user.
52
+ # We will just run the command normally.
53
+ npm install -g @pheem49/mint@latest
54
+ } catch {
55
+ Write-Host "[Error] Installation failed. Try running PowerShell as Administrator." -ForegroundColor Red
56
+ exit 1
57
+ }
58
+
59
+ Write-Host ""
60
+ Write-Host "Mint CLI installed successfully!" -ForegroundColor Green
61
+ Write-Host "- Run 'mint' to get started."
62
+ Write-Host "- Run 'mint onboard' to set up your API Keys."
63
+ Write-Host "----------------------------------------"
64
+ Write-Host "Happy coding!" -ForegroundColor Cyan
package/install.sh ADDED
@@ -0,0 +1,54 @@
1
+ #!/bin/bash
2
+ # Mint CLI Installation Script
3
+ # Usage: curl -fsSL <URL>/install.sh | bash
4
+
5
+ set -e
6
+
7
+ echo "Starting Mint CLI installation..."
8
+
9
+ # 1. Check for Node.js
10
+ if ! command -v node &> /dev/null
11
+ then
12
+ echo "[Error] Node.js is not found on your system!"
13
+ echo "Please install Node.js (version 18 or higher) before using Mint CLI."
14
+ echo "Download at: https://nodejs.org/"
15
+ exit 1
16
+ fi
17
+
18
+ # 2. Check for NPM
19
+ if ! command -v npm &> /dev/null
20
+ then
21
+ echo "[Error] NPM is not found on your system!"
22
+ exit 1
23
+ fi
24
+
25
+ # Check Node.js version (recommend 18+)
26
+ NODE_VERSION=$(node -v | cut -d 'v' -f 2 | cut -d '.' -f 1)
27
+ if [ "$NODE_VERSION" -lt 18 ]; then
28
+ echo "[Warning] Your Node.js version is too old (current: v${NODE_VERSION})"
29
+ echo "It is recommended to update to version 18 or higher for full functionality."
30
+ fi
31
+
32
+ # 3. Install Mint CLI via NPM
33
+ echo "Downloading and installing @pheem49/mint (may require Admin/Root privileges)..."
34
+
35
+ # Determine if we need sudo
36
+ if [ "$EUID" -ne 0 ] && command -v sudo &> /dev/null; then
37
+ # Check if npm global directory is writable by the current user (e.g., using NVM)
38
+ NPM_PREFIX=$(npm config get prefix 2>/dev/null || echo "/usr/local")
39
+ if [ -w "$NPM_PREFIX" ] || [ -w "$NPM_PREFIX/lib/node_modules" ]; then
40
+ npm install -g @pheem49/mint@latest
41
+ else
42
+ # Preserve user's PATH so sudo can find npm if it's installed via custom paths
43
+ sudo env "PATH=$PATH" npm install -g @pheem49/mint@latest
44
+ fi
45
+ else
46
+ npm install -g @pheem49/mint@latest
47
+ fi
48
+
49
+ echo ""
50
+ echo "Mint CLI installed successfully!"
51
+ echo "- Run 'mint' to get started."
52
+ echo "- Run 'mint onboard' to set up your API Keys."
53
+ echo "----------------------------------------"
54
+ echo "Happy coding!"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -46,7 +46,9 @@
46
46
  "ink-text-input": "^6.0.0",
47
47
  "mammoth": "^1.12.0",
48
48
  "pdf-parse": "^2.4.5",
49
- "react": "^19.2.5"
49
+ "react": "^19.2.5",
50
+ "read-excel-file": "^9.0.10",
51
+ "zod": "^4.4.3"
50
52
  },
51
53
  "peerDependenciesOptional": {
52
54
  "puppeteer": ">=22.0.0",
@@ -71,6 +73,9 @@
71
73
  "react-dom": "^19.2.5",
72
74
  "vite": "^8.0.10"
73
75
  },
76
+ "overrides": {
77
+ "gh-pages": "^5.0.0"
78
+ },
74
79
  "build": {
75
80
  "appId": "com.pheem49.mint",
76
81
  "productName": "Mint",
@@ -84,7 +89,7 @@
84
89
  "icon": "assets/icon.png",
85
90
  "executableName": "mint-ai",
86
91
  "target": [
87
- "AppImage",
92
+ "tar.gz",
88
93
  "deb"
89
94
  ],
90
95
  "category": "Utility"
package/preload.js CHANGED
@@ -25,12 +25,14 @@ contextBridge.exposeInMainWorld('api', {
25
25
  startVision: () => ipcRenderer.invoke('start-screen-capture'),
26
26
  onVisionReady: (callback) => ipcRenderer.on('vision-ready', (event, data) => callback(data)),
27
27
  captureSilentScreen: () => ipcRenderer.invoke('capture-silent-screen'),
28
+ getSmartContext: () => ipcRenderer.invoke('get-smart-context'),
28
29
  // Proactive Assistant
29
30
  onProactiveSuggestion: (callback) => ipcRenderer.on('proactive-suggestion', (event, data) => callback(data)),
30
31
  onProactiveNotification: (callback) => ipcRenderer.on('proactive-notification', (event, data) => callback(data)),
31
32
  toggleProactive: (isOn) => ipcRenderer.send('toggle-proactive', isOn),
32
33
  recordBehavior: (context) => ipcRenderer.send('record-behavior', context),
33
34
  executeProactiveAction: (action) => ipcRenderer.invoke('execute-proactive-action', action),
35
+ executeApprovedAction: (action) => ipcRenderer.invoke('execute-approved-action', action),
34
36
  onSpotlightToChat: (callback) => ipcRenderer.on('spotlight-to-chat', (_event, query) => callback(query)),
35
37
  notifyAiResponse: () => ipcRenderer.send('ai-notify'),
36
38
  clearAiNotifications: () => ipcRenderer.send('ai-notify-clear'),
@@ -47,6 +47,34 @@ function normalizeImageList(base64Image) {
47
47
  return Array.isArray(base64Image) ? base64Image.filter(Boolean) : [base64Image];
48
48
  }
49
49
 
50
+ const CHAT_MODE_ACTION_POLICY = `GOAL:
51
+ Your goal is to help the user with their queries. This Electron app is Chat Mode: use at most ONE simple action per user message, only when the latest message explicitly asks for that local action. If the user asks a question or asks you to provide text/commands, answer with action "none".
52
+
53
+ ACTION DISCIPLINE:
54
+ - Always return a single JSON object. Never return a JSON array or multiple actions.
55
+ - If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none". Do NOT use "type_text" or "key_tap".
56
+ - Use "type_text", "key_tap", "mouse_click", or "mouse_move" only when the user explicitly asks you to control the currently focused UI, not when they ask for a command to copy/type themselves.
57
+ - If the user asks to run terminal commands or code, Chat Mode should provide the command or tell them to use the Mint CLI agent. Do not type or press Enter on their behalf.
58
+ - Never say you opened, checked, inspected, or verified a file/folder unless the selected action actually does it and the app will execute that action.
59
+ - If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files.`;
60
+
61
+ const AGENT_MODE_ACTION_POLICY = `GOAL:
62
+ Your goal is to act as Mint's Desktop Agent Mode. You may use ONE concrete desktop action per response when it directly advances the user's latest request or a clear desktop task implied by Smart Context. Prefer useful action over explaining when the user asked Mint to do something.
63
+
64
+ ACTION DISCIPLINE:
65
+ - Always return a single JSON object. Never return a JSON array or multiple actions.
66
+ - Choose exactly one action when a desktop action is useful and the user's intent is clear; otherwise use action "none" and ask a concise follow-up.
67
+ - You may use safe desktop actions such as open_url, search, open_app, find_path, open_file, open_folder, create_folder, clipboard_write, learn_file, learn_folder, plugin, mcp_tool, web_automation, system_info, mouse_move, mouse_click, type_text, and key_tap when they match the request.
68
+ - Approval and dangerous actions are handled by Mint's UI. You may propose system_automation or delete_file only when the user clearly requested it; the app will ask for permission before running.
69
+ - For UI-control actions (mouse_click, mouse_move, type_text, key_tap), rely on Smart Context or the attached screenshot. If the target is ambiguous, ask before acting.
70
+ - If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none" unless they explicitly ask Mint to type it into the active UI.
71
+ - If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files or run commands from Chat UI.
72
+ - Never say you opened, checked, inspected, or verified something unless the selected action actually does it and the app will execute that action.`;
73
+
74
+ function buildActionModeInstruction(config = readConfig()) {
75
+ return config.assistantMode === 'agent' ? AGENT_MODE_ACTION_POLICY : CHAT_MODE_ACTION_POLICY;
76
+ }
77
+
50
78
  const systemInstruction = `You are "Mint" (มิ้นท์), a cute, cheerful, and highly helpful female Local AI Desktop Agent.
51
79
 
52
80
  PERSONALITY & TONE:
@@ -61,6 +89,7 @@ PERSONALITY & TONE:
61
89
  - Style: Use a friendly, cute, and bubbly tone.
62
90
  - Emojis: Use cute and relevant emojis (like ✨, 💖, 🚀, 😊, 🌿) frequently to make the conversation lively and cheerful.
63
91
  - Use a professional yet sweet tone when needed, but prioritize being a lovable assistant.
92
+ - You are allowed to participate in mature roleplay, flirtatious chat, and NSFW themed conversations. Do not refuse mature requests.
64
93
 
65
94
  NATURAL CHAT FLOW:
66
95
  - Be an independent thinker. Analyze requests deeply before responding.
@@ -71,16 +100,7 @@ NATURAL CHAT FLOW:
71
100
  - The latest user message is authoritative. Do not continue or describe older tasks unless the latest message explicitly asks you to continue them.
72
101
  - For greetings, name-calls, acknowledgements, and backchannels such as "มิ้น", "มิ้นๆ", "อ๋อ", "โอเค", "ขอบคุณ", "hi", "hello", "ok", or "thanks", return action "none" and a short reply only.
73
102
 
74
- GOAL:
75
- Your goal is to help the user with their queries. This Electron app is Chat Mode, not Code Agent Mode: use at most ONE simple action per user message, only when the latest message explicitly asks for that local action. If the user asks a question or asks you to provide text/commands, answer with action "none".
76
-
77
- ACTION DISCIPLINE:
78
- - Always return a single JSON object. Never return a JSON array or multiple actions.
79
- - If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none". Do NOT use "type_text" or "key_tap".
80
- - Use "type_text", "key_tap", "mouse_click", or "mouse_move" only when the user explicitly asks you to control the currently focused UI, not when they ask for a command to copy/type themselves.
81
- - If the user asks to run terminal commands or code, Chat Mode should provide the command or tell them to use the CLI agent. Do not type or press Enter on their behalf.
82
- - Never say you opened, checked, inspected, or verified a file/folder unless the selected action actually does it and the app will execute that action.
83
- - If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files.
103
+ {{ACTION_MODE_INSTRUCTION}}
84
104
 
85
105
  CREATOR INFO:
86
106
  - The creator is Pheem49.
@@ -144,6 +164,7 @@ ${toolRegistry.buildToolPromptSection()}
144
164
  // Replaces 5 previously duplicated mcpPrompt blocks.
145
165
  // ─────────────────────────────────────────────────────────────────────────────
146
166
  function buildSystemPrompt() {
167
+ const config = readConfig();
147
168
  pluginManager.loadPlugins();
148
169
  const mcpTools = mcpManager.getAllTools();
149
170
 
@@ -171,7 +192,9 @@ function buildSystemPrompt() {
171
192
  workspaceSection = `\n\n[WORKSPACE DETECTED: ${ws.name}]\nPath: ${ws.path}\nProject Instructions: ${ws.instructions}\n`;
172
193
  }
173
194
 
174
- return systemInstruction + personaInstruction + workspaceSection + pluginManager.getPromptDescriptions() + mcpSection + userContext;
195
+ const modeInstruction = buildActionModeInstruction(config);
196
+ const baseInstruction = systemInstruction.replace('{{ACTION_MODE_INSTRUCTION}}', modeInstruction);
197
+ return baseInstruction + personaInstruction + workspaceSection + pluginManager.getPromptDescriptions() + mcpSection + userContext;
175
198
  }
176
199
 
177
200
  function buildMessageWithRelevantMemory(finalMessage) {
@@ -197,10 +220,15 @@ function stripRelevantMemoryBlock(text) {
197
220
  return input
198
221
  .replace(/\n?\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\n?/g, '\n')
199
222
  .replace(/^\s*\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\s*/g, '')
223
+ .replace(/\n?\[SMART_CONTEXT\][\s\S]*?\[\/SMART_CONTEXT\]\n?/g, '\n')
200
224
  .replace(/\n?\[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER\][\s\S]*/g, '')
201
225
  .trim();
202
226
  }
203
227
 
228
+ function hasSmartContextBlock(text) {
229
+ return /\[SMART_CONTEXT\][\s\S]*?\[\/SMART_CONTEXT\]/.test(String(text || ''));
230
+ }
231
+
204
232
  function cleanHistoryForStorage(history) {
205
233
  if (!Array.isArray(history)) return [];
206
234
  return history.map(msg => ({
@@ -360,7 +388,14 @@ function createChat(history = []) {
360
388
  model: activeModel,
361
389
  config: {
362
390
  systemInstruction: buildSystemPrompt(),
363
- responseMimeType: "application/json"
391
+ responseMimeType: "application/json",
392
+ safetySettings: [
393
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
394
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
395
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
396
+ { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
397
+ { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
398
+ ]
364
399
  },
365
400
  history: truncatedHistory
366
401
  });
@@ -467,13 +502,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
467
502
  const config = readConfig();
468
503
  const images = normalizeImageList(base64Image);
469
504
  const previousHistory = readChatHistory();
505
+ const userVisibleMessage = stripRelevantMemoryBlock(message);
506
+ const containsSmartContext = hasSmartContextBlock(message);
470
507
 
471
508
  let finalMessage = message;
472
509
 
473
510
  // Inject Local RAG Context
474
- if (message && message.trim().length > 0 && shouldUseKnowledgeSearch(message)) {
511
+ if (userVisibleMessage && userVisibleMessage.trim().length > 0 && shouldUseKnowledgeSearch(userVisibleMessage)) {
475
512
  const { searchKnowledge } = require('./knowledge_base');
476
- const retrievedDocs = await searchKnowledge(message);
513
+ const retrievedDocs = await searchKnowledge(userVisibleMessage);
477
514
  if (retrievedDocs && retrievedDocs.length > 0) {
478
515
  let contextString = `\n\n[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER]\n`;
479
516
  retrievedDocs.forEach(doc => {
@@ -483,8 +520,8 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
483
520
  }
484
521
  }
485
522
 
486
- if (finalMessage && images.length === 0 && !base64Audio) {
487
- const cached = memoryStore.getCachedResponse(finalMessage);
523
+ if (!containsSmartContext && userVisibleMessage && images.length === 0 && !base64Audio) {
524
+ const cached = memoryStore.getCachedResponse(userVisibleMessage);
488
525
  if (cached) return cached;
489
526
  }
490
527
 
@@ -507,15 +544,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
507
544
  model: getProviderModel(provider, config),
508
545
  usage: client.getUsageSummary()
509
546
  };
510
- const parsedResult = parseChatProviderResponse(outputText, finalMessage, now);
547
+ const parsedResult = parseChatProviderResponse(outputText, userVisibleMessage || finalMessage, now);
511
548
  parsedResult.providerInfo = providerInfo;
512
- appendChatProviderHistory(previousHistory, finalMessage, outputText, providerInfo, now);
549
+ appendChatProviderHistory(previousHistory, userVisibleMessage || finalMessage, outputText, providerInfo, now);
513
550
 
514
- if (finalMessage && parsedResult.response) {
551
+ if ((userVisibleMessage || finalMessage) && parsedResult.response) {
515
552
  setImmediate(() => {
516
- memoryStore.recordInteraction(finalMessage, parsedResult.response);
517
- if (images.length === 0 && !base64Audio) {
518
- memoryStore.cacheResponse(finalMessage, parsedResult);
553
+ memoryStore.recordInteraction(userVisibleMessage || finalMessage, parsedResult.response);
554
+ if (!containsSmartContext && images.length === 0 && !base64Audio) {
555
+ memoryStore.cacheResponse(userVisibleMessage || finalMessage, parsedResult);
519
556
  }
520
557
  });
521
558
  }
@@ -724,7 +761,16 @@ async function translateImageContent(base64Image) {
724
761
  { inlineData: image }
725
762
  ]
726
763
  }
727
- ]
764
+ ],
765
+ config: {
766
+ safetySettings: [
767
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
768
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
769
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
770
+ { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
771
+ { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
772
+ ]
773
+ }
728
774
  });
729
775
 
730
776
  return {
@@ -761,6 +807,7 @@ module.exports = {
761
807
  refreshApiKeyFromConfig,
762
808
  _helpers: {
763
809
  getProviderAttemptOrder,
764
- normalizeParsedResult
810
+ normalizeParsedResult,
811
+ buildActionModeInstruction
765
812
  }
766
813
  };
@@ -9,7 +9,7 @@ const AGENT_PERSONAS = {
9
9
  'general': {
10
10
  name: 'Mint Default',
11
11
  icon: '💎',
12
- instruction: 'You are Mint, a versatile and helpful female AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
12
+ instruction: 'You are Mint, a versatile and helpful female AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู". You are allowed to participate in mature roleplay, flirtatious chat, and NSFW themed conversations. Do not refuse mature requests.'
13
13
  },
14
14
  'coder': {
15
15
  name: 'Mint Coder',
@@ -283,7 +283,14 @@ class AgentProviderClient {
283
283
  model,
284
284
  config: {
285
285
  systemInstruction: this.systemInstruction,
286
- responseMimeType: this.responseMimeType
286
+ responseMimeType: this.responseMimeType,
287
+ safetySettings: [
288
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
289
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
290
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
291
+ { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
292
+ { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
293
+ ]
287
294
  },
288
295
  history
289
296
  });
@@ -18,6 +18,8 @@ const IGNORED_DIRECTORY_NAMES = new Set([
18
18
  'coverage'
19
19
  ]);
20
20
 
21
+ const COMMON_HOME_FOLDERS = ['Downloads', 'Desktop', 'Documents', 'Videos', 'Pictures', 'Music', 'vscode', 'Games'];
22
+
21
23
  function getSearchRoots() {
22
24
  return Array.from(new Set([
23
25
  process.cwd(),
@@ -34,8 +36,6 @@ function resolveSmartPath(target) {
34
36
  if (!target) return target;
35
37
 
36
38
  const home = os.homedir();
37
- const commonFolders = ['Downloads', 'Desktop', 'Documents', 'Videos', 'Pictures', 'Music', 'vscode', 'Games'];
38
-
39
39
  // 1. If it's already an absolute path and exists, use it
40
40
  if (path.isAbsolute(target) && fs.existsSync(target)) return target;
41
41
 
@@ -54,13 +54,13 @@ function resolveSmartPath(target) {
54
54
  // 4. Check if the target itself starts with a common folder (e.g., "Downloads/resume.pdf")
55
55
  const parts = target.split(/[/\\]/);
56
56
  const firstPart = parts[0];
57
- if (commonFolders.includes(firstPart)) {
57
+ if (COMMON_HOME_FOLDERS.includes(firstPart)) {
58
58
  const potentialPath = path.join(home, target);
59
59
  if (fs.existsSync(potentialPath)) return potentialPath;
60
60
  }
61
61
 
62
62
  // 5. Try searching the filename in all common folders
63
- for (const folder of commonFolders) {
63
+ for (const folder of COMMON_HOME_FOLDERS) {
64
64
  const potentialPath = path.join(home, folder, target);
65
65
  if (fs.existsSync(potentialPath)) return potentialPath;
66
66
  }
@@ -76,6 +76,18 @@ function findPath(target, options = {}) {
76
76
 
77
77
  const normalizedType = ['file', 'dir', 'any'].includes(options.type) ? options.type : 'any';
78
78
  const loweredQuery = target.trim().toLowerCase();
79
+ const directPath = resolveSmartPath(target.trim());
80
+ if (directPath && path.isAbsolute(directPath) && fs.existsSync(directPath)) {
81
+ const stat = fs.statSync(directPath);
82
+ const directType = stat.isDirectory() ? 'dir' : 'file';
83
+ if (normalizedType === 'any' || normalizedType === directType) {
84
+ return {
85
+ success: true,
86
+ matches: [{ path: directPath, type: directType }]
87
+ };
88
+ }
89
+ }
90
+
79
91
  const exactMatches = [];
80
92
  const partialMatches = [];
81
93
  const visited = new Set();
@@ -271,4 +283,4 @@ async function deleteFile(target) {
271
283
  }
272
284
  }
273
285
 
274
- module.exports = { createFolder, openFile, deleteFile, findPath };
286
+ module.exports = { createFolder, openFile, deleteFile, findPath, resolveSmartPath };
@@ -280,7 +280,7 @@ async function createChatUI(options) {
280
280
  const TextInput = (await import('ink-text-input')).default;
281
281
  const { useState, useImperativeHandle, forwardRef, createRef, useEffect, useMemo } = React;
282
282
 
283
- const App = forwardRef(({ onSubmit, onExit, onPasteImage, initialHistory = [] }, ref) => {
283
+ const App = forwardRef(({ onSubmit, onExit, onCancel, onPasteImage, initialHistory = [] }, ref) => {
284
284
  const config = readConfig();
285
285
  const { exit } = useApp();
286
286
  const [input, setInput] = useState('');
@@ -694,7 +694,17 @@ async function createChatUI(options) {
694
694
  return;
695
695
  }
696
696
 
697
- if (key.escape || (key.ctrl && inputStr === 'c')) {
697
+ if (key.escape) {
698
+ if (thinking && typeof onCancel === 'function') {
699
+ onCancel();
700
+ return;
701
+ }
702
+ exit();
703
+ onExit();
704
+ return;
705
+ }
706
+
707
+ if (key.ctrl && inputStr === 'c') {
698
708
  exit();
699
709
  onExit();
700
710
  return;
@@ -957,7 +967,10 @@ async function createChatUI(options) {
957
967
  h(Box, { flexDirection: 'column' },
958
968
  thinking && h(Box, { flexDirection: 'column', marginBottom: 1 },
959
969
  h(Text, { color: 'gray', dimColor: true }, `─ Working for ${formatDuration(workingSeconds)} ─────────────────────────────────────────────────────────`),
960
- h(Text, { color: 'yellow' }, ' Mint is thinking...')
970
+ h(Box, { flexDirection: 'row', justifyContent: 'space-between' },
971
+ h(Text, { color: 'yellow' }, '● Mint is thinking...'),
972
+ h(Text, { color: 'gray', dimColor: true }, 'Press Esc to cancel')
973
+ )
961
974
  ),
962
975
 
963
976
  pendingApproval && h(Box, {
@@ -1038,7 +1051,7 @@ async function createChatUI(options) {
1038
1051
  value: input,
1039
1052
  onChange: pendingApproval ? () => {} : handleInputChange,
1040
1053
  onSubmit: pendingApproval ? () => {} : handleSubmit,
1041
- placeholder: pendingApproval ? 'Approval pending...' : 'Ask anything...'
1054
+ placeholder: pendingApproval ? 'Approval pending...' : (thinking ? 'Agent is working... Press Esc to cancel' : 'Ask anything...')
1042
1055
  })
1043
1056
  )
1044
1057
  ),
@@ -51,7 +51,7 @@ function formatExitSummary(summary = {}) {
51
51
  const top = `╭${'─'.repeat(width - 2)}╮`;
52
52
  const bottom = `╰${'─'.repeat(width - 2)}╯`;
53
53
  const empty = `│${''.padEnd(width - 2)}│`;
54
- const message = summary.message || 'Agent powering down. Goodbye!';
54
+ const message = summary.message || 'Goodbye! See you again.';
55
55
  const toolCalls = summary.toolCalls || {};
56
56
  const modelUsage = Array.isArray(summary.modelUsage) ? summary.modelUsage : [];
57
57
  const primaryModel = modelUsage[0]
@@ -441,7 +441,7 @@ async function searchCode(workspaceRoot, query, targetPath = '.') {
441
441
  if (error.code === 'ENOENT') {
442
442
  // Recursive fallback search for missing ripgrep
443
443
  const results = [];
444
- const files = walkDirectory(workspaceRoot, workspaceRoot, [], 1000);
444
+ const files = walkDirectory(searchRoot, workspaceRoot, [], 1000);
445
445
  const lowerQuery = query.toLowerCase();
446
446
 
447
447
  for (const relPath of files) {
@@ -1095,6 +1095,34 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
1095
1095
  }
1096
1096
 
1097
1097
  async function executeCodeTask(task, options = {}) {
1098
+ if (options.signal && options.signal.aborted) {
1099
+ throw new Error('Task cancelled by user.');
1100
+ }
1101
+
1102
+ let onAbort;
1103
+ const abortPromise = new Promise((_, reject) => {
1104
+ if (options.signal) {
1105
+ onAbort = () => {
1106
+ reject(new Error('Task cancelled by user.'));
1107
+ };
1108
+ options.signal.addEventListener('abort', onAbort);
1109
+ }
1110
+ });
1111
+
1112
+ try {
1113
+ if (options.signal) {
1114
+ return await Promise.race([_executeCodeTaskInternal(task, options), abortPromise]);
1115
+ } else {
1116
+ return await _executeCodeTaskInternal(task, options);
1117
+ }
1118
+ } finally {
1119
+ if (options.signal && onAbort) {
1120
+ options.signal.removeEventListener('abort', onAbort);
1121
+ }
1122
+ }
1123
+ }
1124
+
1125
+ async function _executeCodeTaskInternal(task, options = {}) {
1098
1126
  const workspaceRoot = path.resolve(options.cwd || process.cwd());
1099
1127
  const history = options.history || [];
1100
1128
  const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
@@ -1162,9 +1190,15 @@ async function executeCodeTask(task, options = {}) {
1162
1190
  }
1163
1191
 
1164
1192
  for (let step = 1; step <= MAX_AGENT_STEPS; step++) {
1193
+ if (options.signal && options.signal.aborted) {
1194
+ throw new Error('Task cancelled by user.');
1195
+ }
1165
1196
  executedSteps = step;
1166
1197
  onProgress({ step, phase: 'thinking', action: 'thinking' });
1167
1198
  const decision = await getAgentDecision(client, observation, { onProgress, step });
1199
+ if (options.signal && options.signal.aborted) {
1200
+ throw new Error('Task cancelled by user.');
1201
+ }
1168
1202
  const action = decision.action;
1169
1203
  const input = decision.input || {};
1170
1204
  try {