@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.
- package/BUILD_AND_RELEASE.md +75 -0
- package/LICENSE +654 -0
- package/README.md +165 -0
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/assets/icon.png +0 -0
- package/benchmark_ai.js +71 -0
- package/main.js +968 -0
- package/mint-cli-logic.js +71 -0
- package/mint-cli.js +239 -0
- package/package.json +60 -0
- package/preload-picker.js +11 -0
- package/preload-settings.js +11 -0
- package/preload.js +37 -0
- package/privacy.txt +1 -0
- package/src/AI_Brain/Gemini_API.js +419 -0
- package/src/AI_Brain/autonomous_brain.js +139 -0
- package/src/AI_Brain/behavior_memory.js +114 -0
- package/src/AI_Brain/headless_agent.js +120 -0
- package/src/AI_Brain/knowledge_base.js +222 -0
- package/src/AI_Brain/proactive_engine.js +168 -0
- package/src/Automation_Layer/browser_automation.js +147 -0
- package/src/Automation_Layer/file_operations.js +80 -0
- package/src/Automation_Layer/open_app.js +56 -0
- package/src/Automation_Layer/open_website.js +38 -0
- package/src/CLI/chat_ui.js +468 -0
- package/src/CLI/list_features.js +56 -0
- package/src/CLI/onboarding.js +60 -0
- package/src/Command_Parser/parser.js +34 -0
- package/src/Plugins/dev_tools.js +41 -0
- package/src/Plugins/discord.js +20 -0
- package/src/Plugins/docker.js +45 -0
- package/src/Plugins/google_calendar.js +26 -0
- package/src/Plugins/obsidian.js +54 -0
- package/src/Plugins/plugin_manager.js +81 -0
- package/src/Plugins/spotify.js +45 -0
- package/src/Plugins/system_metrics.js +31 -0
- package/src/System/chat_history_manager.js +57 -0
- package/src/System/config_manager.js +73 -0
- package/src/System/custom_workflows.js +127 -0
- package/src/System/daemon_manager.js +67 -0
- package/src/System/system_automation.js +88 -0
- package/src/System/system_events.js +79 -0
- package/src/System/system_info.js +55 -0
- package/src/System/task_manager.js +85 -0
- package/src/UI/floating.css +80 -0
- package/src/UI/floating.html +17 -0
- package/src/UI/floating.js +67 -0
- package/src/UI/index.html +126 -0
- package/src/UI/preload-floating.js +7 -0
- package/src/UI/preload-spotlight.js +10 -0
- package/src/UI/preload-widget.js +5 -0
- package/src/UI/proactive-glow.html +42 -0
- package/src/UI/renderer.js +978 -0
- package/src/UI/screenPicker.html +214 -0
- package/src/UI/screenPicker.js +262 -0
- package/src/UI/settings.css +705 -0
- package/src/UI/settings.html +396 -0
- package/src/UI/settings.js +514 -0
- package/src/UI/spotlight.css +119 -0
- package/src/UI/spotlight.html +23 -0
- package/src/UI/spotlight.js +181 -0
- package/src/UI/styles.css +627 -0
- package/src/UI/widget.css +218 -0
- package/src/UI/widget.html +29 -0
- package/src/UI/widget.js +10 -0
- package/tech_news.txt +3 -0
- 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 };
|