@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,127 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { app, shell } = require('electron');
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
|
|
6
|
+
class CustomWorkflows {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.configPath = path.join(app.getPath('userData'), 'workflows.json');
|
|
9
|
+
this.workflows = [];
|
|
10
|
+
this.lastTriggered = {};
|
|
11
|
+
this.cooldownMs = 60 * 60 * 1000; // 1 hour cooldown per rule
|
|
12
|
+
this.checkIntervalMs = 15000; // 15 seconds poll rate
|
|
13
|
+
this.timer = null;
|
|
14
|
+
this.webContents = null;
|
|
15
|
+
|
|
16
|
+
this.ensureConfigExists();
|
|
17
|
+
this.loadWorkflows();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
ensureConfigExists() {
|
|
21
|
+
if (!fs.existsSync(this.configPath)) {
|
|
22
|
+
const defaultWorkflows = [
|
|
23
|
+
{
|
|
24
|
+
id: "wf-1",
|
|
25
|
+
name: "Check Mic on Zoom",
|
|
26
|
+
trigger: { type: "process_running", processName: "zoom" },
|
|
27
|
+
action: { type: "system_info", message: "Looks like you opened Zoom! Should I check your system resources? 📸", target: "" }
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "wf-2",
|
|
31
|
+
name: "Coding Time",
|
|
32
|
+
trigger: { type: "process_running", processName: "code" },
|
|
33
|
+
action: { type: "open_app", target: "spotify", message: "Coding time! Want me to open Spotify for you? 🎧" }
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
fs.writeFileSync(this.configPath, JSON.stringify(defaultWorkflows, null, 4), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
loadWorkflows() {
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(this.configPath, 'utf-8');
|
|
43
|
+
this.workflows = JSON.parse(raw);
|
|
44
|
+
console.log(`[CustomWorkflows] Loaded ${this.workflows.length} rules.`);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error("[CustomWorkflows] Failed to load workflows.json", e);
|
|
47
|
+
this.workflows = [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
startMonitoring(webContents) {
|
|
52
|
+
this.webContents = webContents;
|
|
53
|
+
if (this.timer) clearInterval(this.timer);
|
|
54
|
+
this.timer = setInterval(() => this.checkProcesses(), this.checkIntervalMs);
|
|
55
|
+
console.log('[CustomWorkflows] Started process monitoring.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stopMonitoring() {
|
|
59
|
+
if (this.timer) {
|
|
60
|
+
clearInterval(this.timer);
|
|
61
|
+
this.timer = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
openConfigFile() {
|
|
66
|
+
// Try to open directly in VS Code since .json might natively open in a browser
|
|
67
|
+
exec(`code "${this.configPath}"`, (err) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
// Fallback: Open the folder containing the config file
|
|
70
|
+
shell.showItemInFolder(this.configPath);
|
|
71
|
+
console.log("[CustomWorkflows] Opened folder instead since VS Code wasn't found in PATH.");
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async checkProcesses() {
|
|
77
|
+
if (!this.workflows || this.workflows.length === 0) return;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Linux check using ps to get process names
|
|
81
|
+
exec('ps -A -o comm=', (err, stdout) => {
|
|
82
|
+
if (err) return;
|
|
83
|
+
const runningProcesses = stdout.toLowerCase();
|
|
84
|
+
|
|
85
|
+
for (const wf of this.workflows) {
|
|
86
|
+
if (wf.trigger && wf.trigger.type === 'process_running' && wf.trigger.processName) {
|
|
87
|
+
const targetName = wf.trigger.processName.toLowerCase();
|
|
88
|
+
// simplistic exact-word match to avoid partial matches
|
|
89
|
+
const regex = new RegExp(`^${targetName}$`, 'm');
|
|
90
|
+
const isRunning = regex.test(runningProcesses);
|
|
91
|
+
|
|
92
|
+
if (isRunning) {
|
|
93
|
+
const lastTime = this.lastTriggered[wf.id] || 0;
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
|
|
96
|
+
// Cooldown mechanism
|
|
97
|
+
if (now - lastTime > this.cooldownMs) {
|
|
98
|
+
this.triggerWorkflow(wf);
|
|
99
|
+
this.lastTriggered[wf.id] = now;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error("Workflow check error:", error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
triggerWorkflow(wf) {
|
|
111
|
+
if (!this.webContents || this.webContents.isDestroyed()) return;
|
|
112
|
+
|
|
113
|
+
console.log(`[CustomWorkflows] Triggering workflow: ${wf.name}`);
|
|
114
|
+
|
|
115
|
+
const suggestion = {
|
|
116
|
+
message: wf.action.message || `💡 Automation triggered: ${wf.name}`,
|
|
117
|
+
suggestions: [
|
|
118
|
+
{ label: "Yes, please", action: wf.action },
|
|
119
|
+
{ label: "Dismiss", action: { type: "none" } }
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.webContents.send('proactive-suggestion', suggestion);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = new CustomWorkflows();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Installs Mint as a systemd user service
|
|
8
|
+
*/
|
|
9
|
+
async function installDaemon() {
|
|
10
|
+
if (process.platform !== 'linux') {
|
|
11
|
+
throw new Error('Daemon installation is currently only supported on Linux (systemd).');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const homeDir = os.homedir();
|
|
15
|
+
const serviceDir = path.join(homeDir, '.config', 'systemd', 'user');
|
|
16
|
+
const servicePath = path.join(serviceDir, 'mint-agent.service');
|
|
17
|
+
|
|
18
|
+
// Create systemd user directory if it doesn't exist
|
|
19
|
+
if (!fs.existsSync(serviceDir)) {
|
|
20
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const nodePath = execSync('which node').toString().trim() || '/usr/bin/node';
|
|
24
|
+
const projectPath = path.resolve(__dirname, '../../');
|
|
25
|
+
const cliPath = path.join(projectPath, 'mint-cli.js');
|
|
26
|
+
|
|
27
|
+
const serviceContent = `[Unit]
|
|
28
|
+
Description=Mint AI Background Agent
|
|
29
|
+
After=network.target
|
|
30
|
+
|
|
31
|
+
[Service]
|
|
32
|
+
Type=simple
|
|
33
|
+
WorkingDirectory=${projectPath}
|
|
34
|
+
ExecStart=${nodePath} ${cliPath} agent
|
|
35
|
+
Restart=always
|
|
36
|
+
RestartSec=10
|
|
37
|
+
|
|
38
|
+
[Install]
|
|
39
|
+
WantedBy=default.target
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(servicePath, serviceContent);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
console.log('[Daemon] Reloading systemd user daemon...');
|
|
46
|
+
execSync('systemctl --user daemon-reload');
|
|
47
|
+
|
|
48
|
+
console.log(`[Daemon] Enabling and starting mint-agent.service...`);
|
|
49
|
+
execSync('systemctl --user enable mint-agent.service');
|
|
50
|
+
execSync('systemctl --user start mint-agent.service');
|
|
51
|
+
|
|
52
|
+
return `Mint Agent installed and started! Check logs with: journalctl --user -u mint-agent -f`;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw new Error(`Failed to configure systemd: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function stopDaemon() {
|
|
59
|
+
try {
|
|
60
|
+
execSync('systemctl --user stop mint-agent.service');
|
|
61
|
+
return "Daemon stopped.";
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw new Error(`Failed to stop daemon: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { installDaemon, stopDaemon };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const { exec } = require('child_process');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes a shell command and returns a promise.
|
|
5
|
+
*/
|
|
6
|
+
function execPromise(command) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
exec(command, (error, stdout, stderr) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
reject(error);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
resolve(stdout.trim());
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Linux System Automation Logic
|
|
20
|
+
*/
|
|
21
|
+
const SystemAutomation = {
|
|
22
|
+
// Volume Control (Using amixer / pulseaudio)
|
|
23
|
+
async setVolume(percent) {
|
|
24
|
+
// Try amixer first (common on many distros)
|
|
25
|
+
try {
|
|
26
|
+
await execPromise(`amixer -D pulse sset Master ${percent}%`);
|
|
27
|
+
return `Volume set to ${percent}%`;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
try {
|
|
30
|
+
await execPromise(`pactl set-sink-volume @DEFAULT_SINK@ ${percent}%`);
|
|
31
|
+
return `Volume set to ${percent}%`;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error("Failed to set volume. amixer or pactl not found.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async mute() {
|
|
39
|
+
try {
|
|
40
|
+
await execPromise(`amixer -D pulse sset Master toggle`);
|
|
41
|
+
return "Volume toggled (mute/unmute)";
|
|
42
|
+
} catch (e) {
|
|
43
|
+
await execPromise(`pactl set-sink-mute @DEFAULT_SINK@ toggle`);
|
|
44
|
+
return "Volume toggled (mute/unmute)";
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Brightness Control (Using brightnessctl or xbacklight)
|
|
49
|
+
async setBrightness(percent) {
|
|
50
|
+
try {
|
|
51
|
+
// brightnessctl is modern and common on Wayland/X11
|
|
52
|
+
await execPromise(`brightnessctl set ${percent}%`);
|
|
53
|
+
return `Brightness set to ${percent}%`;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
try {
|
|
56
|
+
await execPromise(`xbacklight -set ${percent}`);
|
|
57
|
+
return `Brightness set to ${percent}%`;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new Error("Failed to set brightness. brightnessctl or xbacklight not found.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Power Management
|
|
65
|
+
async sleep() {
|
|
66
|
+
return execPromise('systemctl suspend');
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async restart() {
|
|
70
|
+
return execPromise('systemctl reboot');
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async shutdown() {
|
|
74
|
+
return execPromise('systemctl poweroff');
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Window Management (Minimal implementation using xdotool if available)
|
|
78
|
+
async minimizeAll() {
|
|
79
|
+
try {
|
|
80
|
+
await execPromise('xdotool key Super+d');
|
|
81
|
+
return "Minimized all windows";
|
|
82
|
+
} catch (e) {
|
|
83
|
+
throw new Error("xdotool not found. Cannot perform window management.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
module.exports = SystemAutomation;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { exec } = require('child_process');
|
|
2
|
+
const EventEmitter = require('events');
|
|
3
|
+
|
|
4
|
+
class SystemEvents extends EventEmitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.lastBatteryLevel = null;
|
|
8
|
+
this.lastConnectionStatus = null;
|
|
9
|
+
this.checkInterval = 60000; // 1 minute
|
|
10
|
+
this.isMonitoring = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
startMonitoring() {
|
|
14
|
+
if (this.isMonitoring) return;
|
|
15
|
+
this.isMonitoring = true;
|
|
16
|
+
this.check();
|
|
17
|
+
this.timer = setInterval(() => this.check(), this.checkInterval);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
stopMonitoring() {
|
|
21
|
+
if (this.timer) clearInterval(this.timer);
|
|
22
|
+
this.isMonitoring = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async check() {
|
|
26
|
+
await this.checkBattery();
|
|
27
|
+
await this.checkNetwork();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async checkBattery() {
|
|
31
|
+
try {
|
|
32
|
+
// Linux: upower -i $(upower -e | grep 'BAT') | grep -E "percentage"
|
|
33
|
+
const cmd = "upower -i $(upower -e | grep 'BAT') | grep -E 'percentage' | awk '{print $2}' | tr -d '%'";
|
|
34
|
+
const output = await this.execPromise(cmd);
|
|
35
|
+
const level = parseInt(output);
|
|
36
|
+
|
|
37
|
+
if (isNaN(level)) return;
|
|
38
|
+
|
|
39
|
+
// Notify if battery is low (below 20%) and has dropped
|
|
40
|
+
if (level <= 20 && (this.lastBatteryLevel === null || this.lastBatteryLevel > 20)) {
|
|
41
|
+
this.emit('low-battery', level);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.lastBatteryLevel = level;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// Ignore if upower fails (e.g. desktop)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async checkNetwork() {
|
|
51
|
+
try {
|
|
52
|
+
// Check internet connection
|
|
53
|
+
const online = await this.execPromise("ping -c 1 8.8.8.8 > /dev/null && echo 'online' || echo 'offline'");
|
|
54
|
+
const isOnline = online === 'online';
|
|
55
|
+
|
|
56
|
+
if (this.lastConnectionStatus !== null && this.lastConnectionStatus !== isOnline) {
|
|
57
|
+
this.emit('connection-change', isOnline);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.lastConnectionStatus = isOnline;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Ignore ping errors
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
execPromise(command) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
exec(command, (error, stdout) => {
|
|
69
|
+
if (error) {
|
|
70
|
+
resolve('');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(stdout.trim());
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = new SystemEvents();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ดึงข้อมูล RAM, CPU, เวลาปัจจุบัน
|
|
5
|
+
*/
|
|
6
|
+
function getSystemInfo() {
|
|
7
|
+
const totalRAM = os.totalmem();
|
|
8
|
+
const freeRAM = os.freemem();
|
|
9
|
+
const usedRAM = totalRAM - freeRAM;
|
|
10
|
+
const ramPercent = ((usedRAM / totalRAM) * 100).toFixed(1);
|
|
11
|
+
|
|
12
|
+
const now = new Date();
|
|
13
|
+
const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit' });
|
|
14
|
+
const dateStr = now.toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' });
|
|
15
|
+
|
|
16
|
+
const cpuModel = os.cpus()[0]?.model || 'Unknown CPU';
|
|
17
|
+
const cpuCores = os.cpus().length;
|
|
18
|
+
const platform = os.platform();
|
|
19
|
+
const hostname = os.hostname();
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
ram: {
|
|
23
|
+
total: (totalRAM / 1024 / 1024 / 1024).toFixed(2) + ' GB',
|
|
24
|
+
used: (usedRAM / 1024 / 1024 / 1024).toFixed(2) + ' GB',
|
|
25
|
+
free: (freeRAM / 1024 / 1024 / 1024).toFixed(2) + ' GB',
|
|
26
|
+
percent: ramPercent + '%'
|
|
27
|
+
},
|
|
28
|
+
cpu: {
|
|
29
|
+
model: cpuModel,
|
|
30
|
+
cores: cpuCores
|
|
31
|
+
},
|
|
32
|
+
time: timeStr,
|
|
33
|
+
date: dateStr,
|
|
34
|
+
platform,
|
|
35
|
+
hostname
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ดึงข้อมูลอากาศจาก wttr.in (ไม่ต้อง API key)
|
|
41
|
+
* @param {string} city - ชื่อเมือง เช่น Bangkok
|
|
42
|
+
*/
|
|
43
|
+
async function getWeather(city = 'Bangkok') {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=3&lang=th`);
|
|
46
|
+
if (!response.ok) throw new Error('Weather fetch failed');
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
return { success: true, data: text.trim() };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('getWeather error:', err);
|
|
51
|
+
return { success: false, data: 'ไม่สามารถดึงข้อมูลอากาศได้ในขณะนี้' };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { getSystemInfo, getWeather };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
// Standard location for Mint tasks
|
|
6
|
+
const MINT_DIR = path.join(os.homedir(), '.mint');
|
|
7
|
+
const TASKS_FILE = path.join(MINT_DIR, 'tasks.json');
|
|
8
|
+
|
|
9
|
+
// Ensure directory exists
|
|
10
|
+
if (!fs.existsSync(MINT_DIR)) {
|
|
11
|
+
fs.mkdirSync(MINT_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Task Statuses:
|
|
16
|
+
* - 'pending': Waiting for agent to pick up
|
|
17
|
+
* - 'running': Agent is currently working on it
|
|
18
|
+
* - 'completed': Done
|
|
19
|
+
* - 'failed': Error occurred
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
function readTasks() {
|
|
23
|
+
if (!fs.existsSync(TASKS_FILE)) return [];
|
|
24
|
+
try {
|
|
25
|
+
const content = fs.readFileSync(TASKS_FILE, 'utf8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error('[TaskManager] Error reading tasks:', e.message);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeTasks(tasks) {
|
|
34
|
+
try {
|
|
35
|
+
fs.writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('[TaskManager] Error writing tasks:', e.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function addTask(description) {
|
|
42
|
+
const tasks = readTasks();
|
|
43
|
+
const newTask = {
|
|
44
|
+
id: Date.now().toString(),
|
|
45
|
+
description,
|
|
46
|
+
status: 'pending',
|
|
47
|
+
createdAt: new Date().toISOString(),
|
|
48
|
+
updatedAt: new Date().toISOString(),
|
|
49
|
+
steps: [],
|
|
50
|
+
result: null
|
|
51
|
+
};
|
|
52
|
+
tasks.push(newTask);
|
|
53
|
+
writeTasks(tasks);
|
|
54
|
+
return newTask;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getPendingTask() {
|
|
58
|
+
const tasks = readTasks();
|
|
59
|
+
return tasks.find(t => t.status === 'pending');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function updateTask(id, updates) {
|
|
63
|
+
const tasks = readTasks();
|
|
64
|
+
const idx = tasks.findIndex(t => t.id === id);
|
|
65
|
+
if (idx !== -1) {
|
|
66
|
+
tasks[idx] = { ...tasks[idx], ...updates, updatedAt: new Date().toISOString() };
|
|
67
|
+
writeTasks(tasks);
|
|
68
|
+
return tasks[idx];
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function clearCompletedTasks() {
|
|
74
|
+
const tasks = readTasks();
|
|
75
|
+
const activeTasks = tasks.filter(t => t.status === 'pending' || t.status === 'running');
|
|
76
|
+
writeTasks(activeTasks);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
addTask,
|
|
81
|
+
getPendingTask,
|
|
82
|
+
updateTask,
|
|
83
|
+
readTasks,
|
|
84
|
+
clearCompletedTasks
|
|
85
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--badge: #ff4d6d;
|
|
3
|
+
--badge-border: rgba(0, 0, 0, 0.5);
|
|
4
|
+
--ring: rgba(139, 92, 246, 0.5);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
* {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
width: 100vw;
|
|
14
|
+
height: 100vh;
|
|
15
|
+
background: transparent;
|
|
16
|
+
display: grid;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
place-items: center;
|
|
19
|
+
-webkit-app-region: drag;
|
|
20
|
+
font-family: system-ui, sans-serif;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body.dragging {
|
|
24
|
+
cursor: grabbing;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#floating-btn {
|
|
28
|
+
width: 64px;
|
|
29
|
+
height: 64px;
|
|
30
|
+
border: 0;
|
|
31
|
+
border-radius: 50%;
|
|
32
|
+
background: rgba(20, 16, 32, 0.85);
|
|
33
|
+
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35), 0 0 0 2px rgba(255, 255, 255, 0.05) inset;
|
|
34
|
+
display: grid;
|
|
35
|
+
place-items: center;
|
|
36
|
+
position: relative;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
-webkit-app-region: no-drag;
|
|
39
|
+
transition: transform 120ms ease, box-shadow 120ms ease;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#floating-btn:hover {
|
|
43
|
+
transform: translateY(-1px);
|
|
44
|
+
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.4), 0 0 0 2px rgba(255, 255, 255, 0.08) inset;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#floating-btn img {
|
|
48
|
+
width: 36px;
|
|
49
|
+
height: 36px;
|
|
50
|
+
-webkit-user-drag: none;
|
|
51
|
+
user-select: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.badge {
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: -4px;
|
|
57
|
+
right: -4px;
|
|
58
|
+
min-width: 20px;
|
|
59
|
+
height: 20px;
|
|
60
|
+
padding: 0 6px;
|
|
61
|
+
border-radius: 999px;
|
|
62
|
+
background: var(--badge);
|
|
63
|
+
color: white;
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
line-height: 20px;
|
|
66
|
+
text-align: center;
|
|
67
|
+
border: 2px solid var(--badge-border);
|
|
68
|
+
display: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
body.has-unread #floating-btn {
|
|
72
|
+
box-shadow: 0 0 0 3px var(--ring), 0 10px 24px rgba(0, 0, 0, 0.35), 0 0 0 2px rgba(255, 255, 255, 0.05) inset;
|
|
73
|
+
animation: pulse 1.6s ease-in-out infinite;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@keyframes pulse {
|
|
77
|
+
0% { transform: scale(1); }
|
|
78
|
+
50% { transform: scale(1.03); }
|
|
79
|
+
100% { transform: scale(1); }
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self';">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Mint Floating</title>
|
|
8
|
+
<link rel="stylesheet" href="floating.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<button id="floating-btn" aria-label="Open Mint">
|
|
12
|
+
<img src="../../assets/icon.png" alt="Mint" draggable="false" />
|
|
13
|
+
<span id="badge" class="badge" aria-hidden="true">1</span>
|
|
14
|
+
</button>
|
|
15
|
+
<script src="floating.js"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const floatingBtn = document.getElementById('floating-btn');
|
|
2
|
+
const badge = document.getElementById('badge');
|
|
3
|
+
let isDragging = false;
|
|
4
|
+
let didDrag = false;
|
|
5
|
+
let dragStart = null;
|
|
6
|
+
|
|
7
|
+
function startDrag() {
|
|
8
|
+
isDragging = true;
|
|
9
|
+
document.body.classList.add('dragging');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function stopDrag() {
|
|
13
|
+
isDragging = false;
|
|
14
|
+
document.body.classList.remove('dragging');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setBadge(count) {
|
|
18
|
+
const value = Number(count) || 0;
|
|
19
|
+
if (value <= 0) {
|
|
20
|
+
badge.style.display = 'none';
|
|
21
|
+
document.body.classList.remove('has-unread');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
badge.textContent = value > 9 ? '9+' : String(value);
|
|
25
|
+
badge.style.display = 'inline-block';
|
|
26
|
+
document.body.classList.add('has-unread');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
floatingBtn.addEventListener('click', () => {
|
|
30
|
+
if (didDrag) return;
|
|
31
|
+
if (window.floating) window.floating.openMain();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (window.floating) {
|
|
35
|
+
window.floating.onNotify((count) => {
|
|
36
|
+
setBadge(count);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
window.addEventListener('mousedown', (e) => {
|
|
41
|
+
if (e.button !== 0) return;
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
startDrag();
|
|
44
|
+
didDrag = false;
|
|
45
|
+
dragStart = { x: e.screenX, y: e.screenY };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
window.addEventListener('mousemove', (e) => {
|
|
49
|
+
if (!isDragging) return;
|
|
50
|
+
if (dragStart) {
|
|
51
|
+
const dx = e.screenX - dragStart.x;
|
|
52
|
+
const dy = e.screenY - dragStart.y;
|
|
53
|
+
if (!didDrag && Math.hypot(dx, dy) < 4) return;
|
|
54
|
+
didDrag = true;
|
|
55
|
+
}
|
|
56
|
+
if (window.floating) window.floating.dragMove(e.screenX, e.screenY);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
window.addEventListener('mouseup', () => {
|
|
60
|
+
stopDrag();
|
|
61
|
+
dragStart = null;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
window.addEventListener('mouseleave', () => {
|
|
65
|
+
stopDrag();
|
|
66
|
+
dragStart = null;
|
|
67
|
+
});
|