@pheem49/mint 1.2.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.
Files changed (69) hide show
  1. package/BUILD_AND_RELEASE.md +75 -0
  2. package/LICENSE +654 -0
  3. package/README.md +165 -0
  4. package/assets/Agent_Mint.png +0 -0
  5. package/assets/CLI_Screen.png +0 -0
  6. package/assets/Settings.png +0 -0
  7. package/assets/icon.png +0 -0
  8. package/benchmark_ai.js +71 -0
  9. package/main.js +968 -0
  10. package/mint-cli-logic.js +71 -0
  11. package/mint-cli.js +239 -0
  12. package/package.json +60 -0
  13. package/preload-picker.js +11 -0
  14. package/preload-settings.js +11 -0
  15. package/preload.js +37 -0
  16. package/privacy.txt +1 -0
  17. package/src/AI_Brain/Gemini_API.js +419 -0
  18. package/src/AI_Brain/autonomous_brain.js +139 -0
  19. package/src/AI_Brain/behavior_memory.js +114 -0
  20. package/src/AI_Brain/headless_agent.js +120 -0
  21. package/src/AI_Brain/knowledge_base.js +222 -0
  22. package/src/AI_Brain/proactive_engine.js +168 -0
  23. package/src/Automation_Layer/browser_automation.js +147 -0
  24. package/src/Automation_Layer/file_operations.js +80 -0
  25. package/src/Automation_Layer/open_app.js +56 -0
  26. package/src/Automation_Layer/open_website.js +38 -0
  27. package/src/CLI/chat_ui.js +468 -0
  28. package/src/CLI/list_features.js +56 -0
  29. package/src/CLI/onboarding.js +60 -0
  30. package/src/Command_Parser/parser.js +34 -0
  31. package/src/Plugins/dev_tools.js +41 -0
  32. package/src/Plugins/discord.js +20 -0
  33. package/src/Plugins/docker.js +45 -0
  34. package/src/Plugins/google_calendar.js +26 -0
  35. package/src/Plugins/obsidian.js +54 -0
  36. package/src/Plugins/plugin_manager.js +81 -0
  37. package/src/Plugins/spotify.js +45 -0
  38. package/src/Plugins/system_metrics.js +31 -0
  39. package/src/System/chat_history_manager.js +57 -0
  40. package/src/System/config_manager.js +73 -0
  41. package/src/System/custom_workflows.js +127 -0
  42. package/src/System/daemon_manager.js +67 -0
  43. package/src/System/system_automation.js +88 -0
  44. package/src/System/system_events.js +79 -0
  45. package/src/System/system_info.js +55 -0
  46. package/src/System/task_manager.js +85 -0
  47. package/src/UI/floating.css +80 -0
  48. package/src/UI/floating.html +17 -0
  49. package/src/UI/floating.js +67 -0
  50. package/src/UI/index.html +126 -0
  51. package/src/UI/preload-floating.js +7 -0
  52. package/src/UI/preload-spotlight.js +10 -0
  53. package/src/UI/preload-widget.js +5 -0
  54. package/src/UI/proactive-glow.html +42 -0
  55. package/src/UI/renderer.js +978 -0
  56. package/src/UI/screenPicker.html +214 -0
  57. package/src/UI/screenPicker.js +262 -0
  58. package/src/UI/settings.css +705 -0
  59. package/src/UI/settings.html +396 -0
  60. package/src/UI/settings.js +514 -0
  61. package/src/UI/spotlight.css +119 -0
  62. package/src/UI/spotlight.html +23 -0
  63. package/src/UI/spotlight.js +181 -0
  64. package/src/UI/styles.css +627 -0
  65. package/src/UI/widget.css +218 -0
  66. package/src/UI/widget.html +29 -0
  67. package/src/UI/widget.js +10 -0
  68. package/tech_news.txt +3 -0
  69. package/test_knowledge.txt +3 -0
@@ -0,0 +1,60 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { readConfig, writeConfig } = require('../System/config_manager');
4
+ const { installDaemon } = require('../System/daemon_manager');
5
+
6
+ /**
7
+ * Onboarding Wizard for Mint CLI
8
+ */
9
+ async function runOnboarding(options = {}) {
10
+ // Dynamic import for ESM-only inquirer in CommonJS
11
+ const inquirer = (await import('inquirer')).default;
12
+
13
+ console.log('\nWelcome to Mint Onboarding! Let\'s get you set up.\n');
14
+
15
+ const config = readConfig();
16
+
17
+ const questions = [
18
+ {
19
+ type: 'input',
20
+ name: 'apiKey',
21
+ message: 'Please enter your Google Gemini API Key:',
22
+ default: config.apiKey || undefined,
23
+ validate: (input) => input.length > 0 ? true : 'API Key is required.'
24
+ },
25
+ {
26
+ type: 'list',
27
+ name: 'geminiModel',
28
+ message: 'Select the Gemini model to use:',
29
+ choices: [
30
+ 'gemini-2.5-flash',
31
+ 'gemini-3.1-flash-lite-preview',
32
+ 'gemini-3.1-flash-lite',
33
+ 'gemini-2.0-pro-exp-02-05'
34
+ ],
35
+ default: config.geminiModel || 'gemini-2.5-flash'
36
+ }
37
+ ];
38
+
39
+ const answers = await inquirer.prompt(questions);
40
+
41
+ // Save configuration
42
+ const newConfig = { ...config, ...answers };
43
+ writeConfig(newConfig);
44
+ console.log('\n✅ Configuration saved successfully!');
45
+
46
+ // Install Daemon if requested
47
+ if (options.installDaemon) {
48
+ console.log('\n🚀 Installing Mint Background Agent (Daemon)...');
49
+ try {
50
+ const result = await installDaemon();
51
+ console.log(`✅ ${result}`);
52
+ } catch (err) {
53
+ console.error(`❌ Failed to install daemon: ${err.message}`);
54
+ }
55
+ }
56
+
57
+ console.log('\nAll set! You can now use "mint chat" to start talking to me.\n');
58
+ }
59
+
60
+ module.exports = { runOnboarding };
@@ -0,0 +1,34 @@
1
+ function parseCommand(aiResponse) {
2
+ let action = { type: 'none', target: '' };
3
+ let responseText = '';
4
+
5
+ if (typeof aiResponse === 'string') {
6
+ // Attempt to parse string to JSON
7
+ try {
8
+ const parsed = JSON.parse(aiResponse);
9
+ action = parsed.action || action;
10
+ responseText = parsed.response || '';
11
+ } catch (e) {
12
+ // Fallback for markdown
13
+ const jsonMatch = aiResponse.match(/```json\n([\s\S]*?)\n```/) || aiResponse.match(/\{[\s\S]*\}/);
14
+ if (jsonMatch) {
15
+ try {
16
+ const parsed = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
17
+ action = parsed.action || action;
18
+ responseText = parsed.response || '';
19
+ } catch (err) {
20
+ responseText = aiResponse;
21
+ }
22
+ } else {
23
+ responseText = aiResponse;
24
+ }
25
+ }
26
+ } else if (typeof aiResponse === 'object') {
27
+ action = aiResponse.action || action;
28
+ responseText = aiResponse.response || '';
29
+ }
30
+
31
+ return { response: responseText, action };
32
+ }
33
+
34
+ module.exports = { parseCommand };
@@ -0,0 +1,41 @@
1
+ const { exec } = require('child_process');
2
+
3
+ function execPromise(command, cwd) {
4
+ return new Promise((resolve) => {
5
+ exec(command, { cwd }, (error, stdout, stderr) => {
6
+ if (error) {
7
+ resolve(`Error: ${stderr || error.message}`);
8
+ return;
9
+ }
10
+ resolve(stdout.trim());
11
+ });
12
+ });
13
+ }
14
+
15
+ module.exports = {
16
+ name: 'dev_tools',
17
+ description: 'Get git status, recent commits, or branch information for a project. Instruction MUST be "git status", "git log", or "git branch".',
18
+
19
+ async execute(instruction) {
20
+ let cwd = process.cwd();
21
+ let cmd = (instruction || '').toLowerCase();
22
+
23
+ let gitCmd = '';
24
+ if (cmd.includes('status')) {
25
+ gitCmd = 'git status -s';
26
+ } else if (cmd.includes('log') || cmd.includes('commit')) {
27
+ gitCmd = 'git log -n 5 --oneline';
28
+ } else if (cmd.includes('branch')) {
29
+ gitCmd = 'git branch';
30
+ } else {
31
+ return "ไม่เข้าใจคำสั่ง git ค่ะ ระบุเป็น status, log, หรือ branch นะคะ (ตัวอย่าง: git status)";
32
+ }
33
+
34
+ const output = await execPromise(gitCmd, cwd);
35
+ if (!output || output.startsWith('Error:')) {
36
+ return `ไม่สามารถดึงข้อมูล Git ได้ค่ะ: ${output}`;
37
+ }
38
+
39
+ return `ผลลัพธ์จาก Git:\n${output}`;
40
+ }
41
+ };
@@ -0,0 +1,20 @@
1
+ module.exports = {
2
+ name: 'discord',
3
+ description: 'Interacts with Discord. Valid targets are "mute", "unmute", "deafen", "undeafen". (Note: This is currently a placeholder plugin)',
4
+
5
+ async execute(target) {
6
+ return new Promise((resolve) => {
7
+ console.log(`[Discord Plugin] Received command: ${target}`);
8
+
9
+ // In a real implementation, you might use Discord RPC or xdotool
10
+ // For now, it just simulates success.
11
+ const validTargets = ['mute', 'unmute', 'deafen', 'undeafen'];
12
+
13
+ if (!validTargets.includes(target.toLowerCase())) {
14
+ return resolve(`Invalid discord command: ${target}`);
15
+ }
16
+
17
+ resolve(`Simulated Discord command: ${target}`);
18
+ });
19
+ }
20
+ };
@@ -0,0 +1,45 @@
1
+ const { exec } = require('child_process');
2
+
3
+ module.exports = {
4
+ name: 'docker',
5
+ description: 'Interacts with local Docker engine. Valid targets include: "start <container>", "stop <container>", "restart <container>", "list".',
6
+
7
+ async execute(target) {
8
+ return new Promise((resolve) => {
9
+ console.log(`[Docker Plugin] Executing command: ${target}`);
10
+
11
+ const [action, ...args] = target.toLowerCase().split(' ');
12
+ const containerName = args.join(' ');
13
+
14
+ let cmd = '';
15
+
16
+ if (action === 'list') {
17
+ cmd = 'docker ps --format "{{.Names}} ({{.Status}})"';
18
+ } else if (['start', 'stop', 'restart'].includes(action) && containerName) {
19
+ cmd = `docker ${action} ${containerName}`;
20
+ } else {
21
+ return resolve(`Invalid docker command or missing container name: ${target}`);
22
+ }
23
+
24
+ exec(cmd, (error, stdout, stderr) => {
25
+ if (error) {
26
+ if (error.code === 127 || stderr.includes('not found')) {
27
+ return resolve('Error: Docker is not installed or not in PATH.');
28
+ }
29
+ if (stderr.includes('permission denied')) {
30
+ return resolve('Error: Permission denied. You might need to add your user to the "docker" group.');
31
+ }
32
+ return resolve(`Docker Error: ${stderr || error.message}`);
33
+ }
34
+
35
+ if (action === 'list') {
36
+ const containers = stdout.trim();
37
+ if (!containers) return resolve("No running Docker containers found.");
38
+ return resolve(`Running Containers:\n${containers}`);
39
+ }
40
+
41
+ resolve(`Successfully executed "docker ${action}" on container "${containerName}".`);
42
+ });
43
+ });
44
+ }
45
+ };
@@ -0,0 +1,26 @@
1
+ const { shell } = require('electron');
2
+
3
+ module.exports = {
4
+ name: 'google_calendar',
5
+ description: 'Quickly open Google Calendar to add a new event or view the calendar. Instruction should be the event title (e.g., "Meeting with team"). If no title, just put "open".',
6
+
7
+ async execute(instruction) {
8
+ const inst = (instruction || '').trim();
9
+
10
+ if (!inst || inst.toLowerCase() === 'open') {
11
+ shell.openExternal('https://calendar.google.com/');
12
+ return 'กำลังเปิดหน้าต่างปฏิทินให้ค่ะ 📅';
13
+ }
14
+
15
+ // Encode the event title for the URL
16
+ const title = encodeURIComponent(inst);
17
+ const url = `https://calendar.google.com/calendar/r/eventedit?text=${title}`;
18
+
19
+ try {
20
+ shell.openExternal(url);
21
+ return `กำลังเปิดหน้าต่างสร้างกิจกรรม "${inst}" ใน Google Calendar ให้ลูกพี่ค่ะ 📅✨`;
22
+ } catch (e) {
23
+ return `เกิดข้อผิดพลาดในการเปิด Calendar ค่ะ: ${e.message}`;
24
+ }
25
+ }
26
+ };
@@ -0,0 +1,54 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function getNotesDir() {
5
+ let base = process.env.HOME || process.env.USERPROFILE || process.cwd();
6
+ // Default to Documents/Mint_Notes
7
+ const dir = path.join(base, 'Documents', 'Mint_Notes');
8
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
9
+ return dir;
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'obsidian',
14
+ description: 'Manage local Markdown notes (like Obsidian/Notion). Instruction MUST be one of: "list", "read: [filename]", "write: [filename] | [content]".',
15
+
16
+ async execute(instruction) {
17
+ const dir = getNotesDir();
18
+
19
+ if (instruction.startsWith('list')) {
20
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
21
+ if (files.length === 0) return "ยังไม่มีโน้ตอยู่ในระบบค่ะ 📝";
22
+ return `รายการโน้ตทั้งหมด:\n${files.join('\n')}`;
23
+ }
24
+
25
+ if (instruction.startsWith('read:')) {
26
+ let filename = instruction.replace('read:', '').trim();
27
+ if (!filename.endsWith('.md')) filename += '.md';
28
+ const filepath = path.join(dir, filename);
29
+ if (fs.existsSync(filepath)) {
30
+ return `เนื้อหาของโน้ต ${filename}:\n\n${fs.readFileSync(filepath, 'utf8')}`;
31
+ }
32
+ return `ไม่พบโน้ตชื่อ ${filename} ค่ะ ❌`;
33
+ }
34
+
35
+ if (instruction.startsWith('write:')) {
36
+ const parts = instruction.replace('write:', '').split('|');
37
+ if (parts.length < 2) return "รูปแบบคำสั่งไม่ถูกต้องค่ะ ต้องเป็น write: filename | content";
38
+ let filename = parts[0].trim();
39
+ const content = parts.slice(1).join('|').trim();
40
+
41
+ if (!filename.endsWith('.md')) filename += '.md';
42
+ const filepath = path.join(dir, filename);
43
+
44
+ // Log timestamp
45
+ const timestamp = new Date().toLocaleString('th-TH');
46
+ const entry = `\n---บันทึกเมื่อ ${timestamp}---\n${content}\n`;
47
+
48
+ fs.appendFileSync(filepath, entry);
49
+ return `บันทึกข้อความลงในโน้ต ${filename} เรียบร้อยแล้วค่ะ ✅`;
50
+ }
51
+
52
+ return "คำสั่งโน้ตไม่ถูกต้องค่ะ ลองใช้ 'list', 'read: ชื่อไฟล์', หรือ 'write: ชื่อไฟล์ | เนื้อหา' นะคะ";
53
+ }
54
+ };
@@ -0,0 +1,81 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class PluginManager {
5
+ constructor() {
6
+ this.plugins = new Map();
7
+ this.pluginsDir = path.join(__dirname);
8
+ }
9
+
10
+ // Load or reload plugins
11
+ loadPlugins() {
12
+ this.plugins.clear();
13
+
14
+ try {
15
+ if (!fs.existsSync(this.pluginsDir)) return;
16
+
17
+ const files = fs.readdirSync(this.pluginsDir);
18
+ for (const file of files) {
19
+ // Ignore self and non-JS files
20
+ if (file === 'plugin_manager.js' || !file.endsWith('.js')) continue;
21
+
22
+ const pluginPath = path.join(this.pluginsDir, file);
23
+
24
+ // Clear require cache for hot-reloading
25
+ delete require.cache[require.resolve(pluginPath)];
26
+
27
+ try {
28
+ const plugin = require(pluginPath);
29
+ if (this.validatePlugin(plugin)) {
30
+ this.plugins.set(plugin.name, plugin);
31
+ console.log(`[PluginManager] Loaded: ${plugin.name}`);
32
+ } else {
33
+ console.warn(`[PluginManager] Invalid plugin format: ${file}`);
34
+ }
35
+ } catch (err) {
36
+ console.error(`[PluginManager] Error loading plugin ${file}:`, err);
37
+ }
38
+ }
39
+ } catch (err) {
40
+ console.error('[PluginManager] Error accessing plugin directory:', err);
41
+ }
42
+ }
43
+
44
+ validatePlugin(plugin) {
45
+ return plugin
46
+ && typeof plugin.name === 'string'
47
+ && typeof plugin.description === 'string'
48
+ && typeof plugin.execute === 'function';
49
+ }
50
+
51
+ // Returns formatted descriptions for the Gemini prompt
52
+ getPromptDescriptions() {
53
+ if (this.plugins.size === 0) return '';
54
+
55
+ let descriptions = '\nPlugin Actions Available:\n';
56
+ for (const [name, plugin] of this.plugins.entries()) {
57
+ descriptions += `- Plugin: "${name}" | Description: ${plugin.description}\n`;
58
+ }
59
+ return descriptions;
60
+ }
61
+
62
+ // Execute a plugin's action
63
+ async executePlugin(name, instruction) {
64
+ const plugin = this.plugins.get(name);
65
+ if (!plugin) {
66
+ return `Plugin "${name}" not found.`;
67
+ }
68
+
69
+ try {
70
+ console.log(`[PluginManager] Executing ${name} with instruction: "${instruction}"`);
71
+ return await plugin.execute(instruction);
72
+ } catch (err) {
73
+ console.error(`[PluginManager] Error executing plugin ${name}:`, err);
74
+ return `Error executing plugin ${name}: ${err.message}`;
75
+ }
76
+ }
77
+ }
78
+
79
+ // Export a singleton instance
80
+ const pluginManager = new PluginManager();
81
+ module.exports = pluginManager;
@@ -0,0 +1,45 @@
1
+ const { exec } = require('child_process');
2
+
3
+ module.exports = {
4
+ name: 'spotify',
5
+ description: 'Controls Spotify playback (play, pause, next, previous). Only works if Spotify is running. Valid targets are: "play", "pause", "next", "previous".',
6
+
7
+ async execute(target) {
8
+ return new Promise((resolve) => {
9
+ const commandMap = {
10
+ 'play': 'playerctl -p spotify play',
11
+ 'pause': 'playerctl -p spotify pause',
12
+ 'next': 'playerctl -p spotify next',
13
+ 'previous': 'playerctl -p spotify previous'
14
+ };
15
+
16
+ const cmd = commandMap[target.toLowerCase()];
17
+
18
+ if (!cmd) {
19
+ return resolve(`Invalid spotify command: ${target}`);
20
+ }
21
+
22
+ exec(cmd, (error) => {
23
+ if (error) {
24
+ // Check if playerctl is missing or Spotify isn't running
25
+ if (error.message.includes('No players found')) {
26
+ return resolve('Spotify is not currently running or playing anything.');
27
+ }
28
+ if (error.code === 127) {
29
+ return resolve('Error: "playerctl" is not installed on this system. Please install it (e.g., sudo apt install playerctl).');
30
+ }
31
+ return resolve(`Failed to execute Spotify command: ${error.message}`);
32
+ }
33
+
34
+ const actionText = {
35
+ 'play': 'Playing Spotify.',
36
+ 'pause': 'Paused Spotify.',
37
+ 'next': 'Skipped to the next song.',
38
+ 'previous': 'Went back to the previous song.'
39
+ };
40
+
41
+ resolve(actionText[target.toLowerCase()]);
42
+ });
43
+ });
44
+ }
45
+ };
@@ -0,0 +1,31 @@
1
+ const os = require('os');
2
+ const { getSystemInfo } = require('../System/system_info');
3
+
4
+ /**
5
+ * System Metrics Plugin — Provides real-time hardware stats to Gemini
6
+ */
7
+ module.exports = {
8
+ name: 'system_metrics',
9
+ description: 'Get real-time system metrics like CPU usage, RAM, and uptime. Instruction can be "all", "ram", "cpu", or "uptime".',
10
+
11
+ async execute(instruction) {
12
+ const info = getSystemInfo();
13
+ const uptimeMin = Math.floor(os.uptime() / 60);
14
+ const uptimeHours = (uptimeMin / 60).toFixed(1);
15
+
16
+ const inst = (instruction || 'all').toLowerCase();
17
+
18
+ if (inst.includes('ram')) {
19
+ return `ความจำเครื่อง (RAM): ใช้ไป ${info.ram.used} จากทั้งหมด ${info.ram.total} (${info.ram.percent})`;
20
+ }
21
+ if (inst.includes('cpu')) {
22
+ return `หน่วยประมวลผล (CPU): ${info.cpu.model} มีทั้งหมด ${info.cpu.cores} คอร์`;
23
+ }
24
+ if (inst.includes('uptime')) {
25
+ return `เปิดเครื่องมาแล้ว: ${uptimeMin} นาที (${uptimeHours} ชั่วโมง)`;
26
+ }
27
+
28
+ // Default: Return basic summary in Thai for Mint's personality
29
+ return `สรุปสถานะระบบ: RAM ใช้ไป ${info.ram.percent}, CPU ${info.cpu.cores} Cores, เปิดเครื่องมาแล้ว ${uptimeMin} นาทีค่ะ ✨`;
30
+ }
31
+ };
@@ -0,0 +1,57 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ let app;
6
+ try {
7
+ const electron = require('electron');
8
+ app = electron.app;
9
+ } catch (e) {
10
+ app = null;
11
+ }
12
+
13
+ const MINT_DIR = path.join(os.homedir(), '.mint');
14
+ if (!fs.existsSync(MINT_DIR)) {
15
+ fs.mkdirSync(MINT_DIR, { recursive: true });
16
+ }
17
+
18
+ const CHAT_HISTORY_PATH = app && app.getPath
19
+ ? path.join(app.getPath('userData'), 'mint-chat-history.json')
20
+ : path.join(MINT_DIR, 'mint-chat-history.json');
21
+
22
+ function readChatHistory() {
23
+ try {
24
+ if (!fs.existsSync(CHAT_HISTORY_PATH)) {
25
+ return [];
26
+ }
27
+
28
+ const raw = fs.readFileSync(CHAT_HISTORY_PATH, 'utf-8');
29
+ const parsed = JSON.parse(raw);
30
+ return Array.isArray(parsed) ? parsed : [];
31
+ } catch (err) {
32
+ console.error('readChatHistory error:', err);
33
+ return [];
34
+ }
35
+ }
36
+
37
+ function writeChatHistory(history) {
38
+ try {
39
+ const safeHistory = Array.isArray(history) ? history : [];
40
+ fs.writeFileSync(CHAT_HISTORY_PATH, JSON.stringify(safeHistory, null, 2), 'utf-8');
41
+ return { success: true };
42
+ } catch (err) {
43
+ console.error('writeChatHistory error:', err);
44
+ return { success: false, message: err.message };
45
+ }
46
+ }
47
+
48
+ function clearChatHistory() {
49
+ return writeChatHistory([]);
50
+ }
51
+
52
+ module.exports = {
53
+ CHAT_HISTORY_PATH,
54
+ readChatHistory,
55
+ writeChatHistory,
56
+ clearChatHistory
57
+ };
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ let app;
6
+ try {
7
+ const electron = require('electron');
8
+ app = electron.app;
9
+ } catch (e) {
10
+ app = null;
11
+ }
12
+
13
+ const MINT_DIR = path.join(os.homedir(), '.mint');
14
+ if (!fs.existsSync(MINT_DIR)) {
15
+ fs.mkdirSync(MINT_DIR, { recursive: true });
16
+ }
17
+
18
+ const CONFIG_PATH = app && app.getPath
19
+ ? path.join(app.getPath('userData'), 'mint-config.json')
20
+ : path.join(MINT_DIR, 'mint-config.json');
21
+
22
+ const DEFAULT_CONFIG = {
23
+ theme: 'dark',
24
+ accentColor: '#8b5cf6',
25
+ systemTextColor: '#f8fafc',
26
+ customBgStart: '#0f172a',
27
+ customBgEnd: '#1e1b4b',
28
+ customPanelBg: '#1e293b',
29
+ apiKey: '',
30
+ geminiModel: 'gemini-2.5-flash',
31
+ language: 'th-TH',
32
+ automationBrowser: 'chromium',
33
+ proactiveInterval: 60, // seconds between screen captures
34
+ proactiveCooldown: 120, // seconds minimum between actual suggestions
35
+ aiProvider: 'gemini',
36
+ ollamaModel: 'llama3:latest',
37
+ enableVoiceReply: true,
38
+ enableCustomWorkflows: true,
39
+ ttsProvider: 'google',
40
+ ttsVolume: 1.0,
41
+ ttsSpeed: 1.0,
42
+ ttsPitch: 1.0,
43
+ pluginSpotifyEnabled: true,
44
+ pluginCalendarEnabled: false,
45
+ pluginDiscordEnabled: false,
46
+ showDesktopWidget: true
47
+ };
48
+
49
+ function readConfig() {
50
+ try {
51
+ if (!fs.existsSync(CONFIG_PATH)) {
52
+ writeConfig(DEFAULT_CONFIG);
53
+ return DEFAULT_CONFIG;
54
+ }
55
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
56
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
57
+ } catch (err) {
58
+ console.error('readConfig error:', err);
59
+ return DEFAULT_CONFIG;
60
+ }
61
+ }
62
+
63
+ function writeConfig(config) {
64
+ try {
65
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
66
+ return { success: true };
67
+ } catch (err) {
68
+ console.error('writeConfig error:', err);
69
+ return { success: false, message: err.message };
70
+ }
71
+ }
72
+
73
+ module.exports = { readConfig, writeConfig, CONFIG_PATH };