@pheem49/mint 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GUIDE_TH.md +113 -0
- package/README.md +214 -142
- package/assets/CLI_Screen.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/guide.html +632 -0
- package/docs/index.html +5 -4
- package/main.js +66 -894
- package/mint-cli-logic.js +15 -8
- package/mint-cli.js +305 -195
- package/package.json +12 -4
- package/src/AI_Brain/Gemini_API.js +77 -20
- package/src/AI_Brain/agent_orchestrator.js +6 -6
- package/src/AI_Brain/autonomous_brain.js +10 -0
- package/src/AI_Brain/behavior_memory.js +26 -5
- package/src/AI_Brain/headless_agent.js +4 -0
- package/src/AI_Brain/knowledge_base.js +61 -8
- package/src/AI_Brain/memory_store.js +55 -7
- package/src/Automation_Layer/file_operations.js +14 -3
- package/src/CLI/chat_router.js +21 -7
- package/src/CLI/chat_ui.js +264 -710
- package/src/CLI/code_agent.js +370 -124
- package/src/CLI/gmail_auth.js +210 -0
- package/src/CLI/list_features.js +5 -1
- package/src/CLI/onboarding.js +307 -55
- package/src/CLI/updater.js +208 -0
- package/src/Channels/brave_search_bridge.js +35 -0
- package/src/Channels/discord_bridge.js +68 -0
- package/src/Channels/google_search_bridge.js +38 -0
- package/src/Channels/line_bridge.js +60 -0
- package/src/Channels/slack_bridge.js +53 -0
- package/src/Channels/telegram_bridge.js +49 -0
- package/src/Channels/whatsapp_bridge.js +55 -0
- package/src/Command_Parser/parser.js +12 -1
- package/src/Plugins/gmail.js +251 -0
- package/src/Plugins/google_calendar.js +245 -19
- package/src/Plugins/notion.js +256 -0
- package/src/System/action_executor.js +129 -0
- package/src/System/bridge_manager.js +76 -0
- package/src/System/chat_history_manager.js +23 -5
- package/src/System/config_manager.js +41 -7
- package/src/System/custom_workflows.js +31 -2
- package/src/System/google_tts_urls.js +51 -0
- package/src/System/ipc_handlers.js +238 -0
- package/src/System/proactive_loop.js +137 -0
- package/src/System/safety_manager.js +165 -0
- package/src/System/screen_capture.js +175 -0
- package/src/System/task_manager.js +15 -5
- package/src/System/window_manager.js +210 -0
- package/src/UI/renderer.js +33 -7
- package/src/UI/settings.html +24 -0
- package/src/UI/settings.js +14 -4
- package/src/UI/styles.css +14 -1
- package/tests/action_executor_safety.test.js +67 -0
- package/tests/gmail.test.js +135 -0
- package/tests/gmail_auth.test.js +129 -0
- package/tests/google_calendar.test.js +113 -0
- package/tests/google_tts_urls.test.js +24 -0
- package/tests/notion.test.js +121 -0
- package/tests/provider_routing.test.js +17 -1
- package/tests/safety_manager.test.js +40 -0
- package/tests/updater.test.js +32 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const { execFile } = require('child_process');
|
|
2
|
+
const pkg = require('../../package.json');
|
|
3
|
+
|
|
4
|
+
const NPM_COMMAND = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
5
|
+
const DEFAULT_AUTO_UPDATE_INTERVAL_HOURS = 24;
|
|
6
|
+
|
|
7
|
+
function execFilePromise(command, args, options = {}) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
execFile(command, args, options, (error, stdout, stderr) => {
|
|
10
|
+
if (error) {
|
|
11
|
+
error.stdout = stdout;
|
|
12
|
+
error.stderr = stderr;
|
|
13
|
+
reject(error);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
resolve({ stdout, stderr });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseVersion(version) {
|
|
22
|
+
return String(version || '')
|
|
23
|
+
.trim()
|
|
24
|
+
.replace(/^v/, '')
|
|
25
|
+
.split('-')[0]
|
|
26
|
+
.split('.')
|
|
27
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function compareVersions(a, b) {
|
|
31
|
+
const left = parseVersion(a);
|
|
32
|
+
const right = parseVersion(b);
|
|
33
|
+
const length = Math.max(left.length, right.length, 3);
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < length; i++) {
|
|
36
|
+
const l = left[i] || 0;
|
|
37
|
+
const r = right[i] || 0;
|
|
38
|
+
if (l > r) return 1;
|
|
39
|
+
if (l < r) return -1;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeNpmVersionOutput(output) {
|
|
45
|
+
const trimmed = String(output || '').trim();
|
|
46
|
+
if (!trimmed) return '';
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(trimmed);
|
|
50
|
+
if (typeof parsed === 'string') return parsed;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
// npm may return plain text depending on config/version.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return trimmed.replace(/^['"]|['"]$/g, '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getAutoUpdateIntervalMs(config = {}) {
|
|
59
|
+
const hours = Number(config.autoUpdateCheckIntervalHours);
|
|
60
|
+
const safeHours = Number.isFinite(hours) && hours > 0
|
|
61
|
+
? hours
|
|
62
|
+
: DEFAULT_AUTO_UPDATE_INTERVAL_HOURS;
|
|
63
|
+
return safeHours * 60 * 60 * 1000;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function shouldRunAutoUpdate(config = {}, now = Date.now()) {
|
|
67
|
+
if (config.enableAutoUpdate === false) return false;
|
|
68
|
+
|
|
69
|
+
const lastCheck = Date.parse(config.lastUpdateCheckAt || '');
|
|
70
|
+
if (!Number.isFinite(lastCheck)) return true;
|
|
71
|
+
|
|
72
|
+
return now - lastCheck >= getAutoUpdateIntervalMs(config);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getLatestVersion(packageName = pkg.name) {
|
|
76
|
+
const { stdout } = await execFilePromise(NPM_COMMAND, ['view', packageName, 'version', '--json'], {
|
|
77
|
+
maxBuffer: 1024 * 1024
|
|
78
|
+
});
|
|
79
|
+
return normalizeNpmVersionOutput(stdout);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function installLatest(packageName = pkg.name, options = {}) {
|
|
83
|
+
const args = ['install', '-g', `${packageName}@latest`];
|
|
84
|
+
if (options.dryRun) {
|
|
85
|
+
args.push('--dry-run');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return await execFilePromise(NPM_COMMAND, args, {
|
|
89
|
+
maxBuffer: 1024 * 1024 * 8
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatUpdateError(error) {
|
|
94
|
+
const detail = [error.stderr, error.stdout, error.message].filter(Boolean).join('\n').trim();
|
|
95
|
+
if (/EACCES|permission denied|Access is denied/i.test(detail)) {
|
|
96
|
+
return [
|
|
97
|
+
'Update failed because npm does not have permission to modify the global install directory.',
|
|
98
|
+
`Run manually: npm install -g ${pkg.name}@latest`,
|
|
99
|
+
'If your npm global packages require sudo, run that command with sudo.'
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (/E404|404 Not Found|not in this registry/i.test(detail)) {
|
|
104
|
+
return [
|
|
105
|
+
`Could not find ${pkg.name} on the npm registry.`,
|
|
106
|
+
'Publish the package first, or update Mint from the source/release channel you installed from.'
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `Update failed: ${detail || 'Unknown npm error'}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function runUpdate(options = {}) {
|
|
114
|
+
const currentVersion = pkg.version;
|
|
115
|
+
let latestVersion = '';
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
latestVersion = await getLatestVersion(pkg.name);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'error',
|
|
122
|
+
currentVersion,
|
|
123
|
+
latestVersion,
|
|
124
|
+
message: formatUpdateError(error)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!latestVersion) {
|
|
129
|
+
return {
|
|
130
|
+
status: 'error',
|
|
131
|
+
currentVersion,
|
|
132
|
+
latestVersion: '',
|
|
133
|
+
message: 'Could not determine the latest Mint version from npm.'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
138
|
+
if (comparison >= 0) {
|
|
139
|
+
return {
|
|
140
|
+
status: 'current',
|
|
141
|
+
currentVersion,
|
|
142
|
+
latestVersion,
|
|
143
|
+
message: `Mint is already up to date (${currentVersion}).`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (options.checkOnly) {
|
|
148
|
+
return {
|
|
149
|
+
status: 'available',
|
|
150
|
+
currentVersion,
|
|
151
|
+
latestVersion,
|
|
152
|
+
message: `Mint ${latestVersion} is available. Current version: ${currentVersion}.`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
await installLatest(pkg.name, { dryRun: options.dryRun });
|
|
158
|
+
return {
|
|
159
|
+
status: options.dryRun ? 'dry-run' : 'updated',
|
|
160
|
+
currentVersion,
|
|
161
|
+
latestVersion,
|
|
162
|
+
message: options.dryRun
|
|
163
|
+
? `Dry run complete. Mint would update from ${currentVersion} to ${latestVersion}.`
|
|
164
|
+
: `Mint updated from ${currentVersion} to ${latestVersion}. Restart mint to use the new version.`
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
status: 'error',
|
|
169
|
+
currentVersion,
|
|
170
|
+
latestVersion,
|
|
171
|
+
message: formatUpdateError(error)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runStartupAutoUpdate(config, writeConfig, options = {}) {
|
|
177
|
+
const now = options.now || Date.now();
|
|
178
|
+
if (!shouldRunAutoUpdate(config, now)) {
|
|
179
|
+
return {
|
|
180
|
+
status: 'skipped',
|
|
181
|
+
message: 'Auto-update check skipped by cooldown.'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof writeConfig === 'function') {
|
|
186
|
+
writeConfig({
|
|
187
|
+
...config,
|
|
188
|
+
lastUpdateCheckAt: new Date(now).toISOString()
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return await runUpdate({ checkOnly: false });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
compareVersions,
|
|
197
|
+
getLatestVersion,
|
|
198
|
+
installLatest,
|
|
199
|
+
normalizeNpmVersionOutput,
|
|
200
|
+
runUpdate,
|
|
201
|
+
runStartupAutoUpdate,
|
|
202
|
+
shouldRunAutoUpdate,
|
|
203
|
+
_private: {
|
|
204
|
+
parseVersion,
|
|
205
|
+
formatUpdateError,
|
|
206
|
+
getAutoUpdateIntervalMs
|
|
207
|
+
}
|
|
208
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
class BraveSearchBridge {
|
|
4
|
+
constructor(credentials) {
|
|
5
|
+
this.apiKey = credentials.apiKey;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async search(query) {
|
|
9
|
+
if (!this.apiKey) {
|
|
10
|
+
throw new Error('Brave Search API Key is required.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios.get('https://api.search.brave.com/res/v1/web/search', {
|
|
15
|
+
params: { q: query, count: 5 },
|
|
16
|
+
headers: {
|
|
17
|
+
'Accept': 'application/json',
|
|
18
|
+
'Accept-Encoding': 'gzip',
|
|
19
|
+
'X-Subscription-Token': this.apiKey
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const results = response.data.web ? response.data.web.results : [];
|
|
24
|
+
return results.map(item => ({
|
|
25
|
+
title: item.title,
|
|
26
|
+
snippet: item.description,
|
|
27
|
+
link: item.url
|
|
28
|
+
}));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
throw new Error(`Brave Search Failed: ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = BraveSearchBridge;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const { Client, GatewayIntentBits, Partials } = require('discord.js');
|
|
2
|
+
const { handleChat } = require('../AI_Brain/Gemini_API');
|
|
3
|
+
|
|
4
|
+
class DiscordBridge {
|
|
5
|
+
constructor(token) {
|
|
6
|
+
this.token = token;
|
|
7
|
+
this.client = new Client({
|
|
8
|
+
intents: [
|
|
9
|
+
GatewayIntentBits.Guilds,
|
|
10
|
+
GatewayIntentBits.GuildMessages,
|
|
11
|
+
GatewayIntentBits.MessageContent,
|
|
12
|
+
GatewayIntentBits.DirectMessages
|
|
13
|
+
],
|
|
14
|
+
partials: [Partials.Channel]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async connect() {
|
|
19
|
+
this.client.on('ready', () => {
|
|
20
|
+
console.log(`[Discord Bridge] Logged in as ${this.client.user.tag}!`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.client.on('messageCreate', async (message) => {
|
|
24
|
+
// Ignore bot messages
|
|
25
|
+
if (message.author.bot) return;
|
|
26
|
+
|
|
27
|
+
// Handle DMs or Mentions
|
|
28
|
+
const isDM = !message.guild;
|
|
29
|
+
const isMentioned = message.mentions.has(this.client.user);
|
|
30
|
+
|
|
31
|
+
if (isDM || isMentioned) {
|
|
32
|
+
try {
|
|
33
|
+
// Clean up the message if it's a mention
|
|
34
|
+
let cleanContent = message.content;
|
|
35
|
+
if (isMentioned) {
|
|
36
|
+
cleanContent = message.content.replace(`<@!${this.client.user.id}>`, '').replace(`<@${this.client.user.id}>`, '').trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!cleanContent) return;
|
|
40
|
+
|
|
41
|
+
// Show typing indicator
|
|
42
|
+
await message.channel.sendTyping();
|
|
43
|
+
|
|
44
|
+
// Send to Mint AI Brain
|
|
45
|
+
const result = await handleChat(cleanContent);
|
|
46
|
+
|
|
47
|
+
// Reply to user
|
|
48
|
+
if (result && result.response) {
|
|
49
|
+
await message.reply(result.response);
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('[Discord Bridge] Error processing message:', err);
|
|
53
|
+
await message.reply('ขออภัยค่ะ เกิดข้อผิดพลาดบางอย่างในการประมวลผลข้อความ');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await this.client.login(this.token);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async disconnect() {
|
|
62
|
+
if (this.client) {
|
|
63
|
+
await this.client.destroy();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = DiscordBridge;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
class GoogleSearchBridge {
|
|
4
|
+
constructor(credentials) {
|
|
5
|
+
this.apiKey = credentials.apiKey;
|
|
6
|
+
this.cx = credentials.cx; // Custom Search Engine ID
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async search(query) {
|
|
10
|
+
if (!this.apiKey || !this.cx) {
|
|
11
|
+
throw new Error('Google Search API Key and CX are required.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await axios.get('https://www.googleapis.com/customsearch/v1', {
|
|
16
|
+
params: {
|
|
17
|
+
key: this.apiKey,
|
|
18
|
+
cx: this.cx,
|
|
19
|
+
q: query,
|
|
20
|
+
num: 5
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const items = response.data.items || [];
|
|
25
|
+
return items.map(item => ({
|
|
26
|
+
title: item.title,
|
|
27
|
+
snippet: item.snippet,
|
|
28
|
+
link: item.link
|
|
29
|
+
}));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new Error(err.response && err.response.data && err.response.data.error
|
|
32
|
+
? `Google Search API Error: ${err.response.data.error.message}`
|
|
33
|
+
: `Google Search Failed: ${err.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = GoogleSearchBridge;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const line = require('@line/bot-sdk');
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const { handleChat } = require('../AI_Brain/Gemini_API');
|
|
4
|
+
|
|
5
|
+
class LineBridge {
|
|
6
|
+
constructor(credentials) {
|
|
7
|
+
this.config = {
|
|
8
|
+
channelAccessToken: credentials.accessToken,
|
|
9
|
+
channelSecret: credentials.secret,
|
|
10
|
+
};
|
|
11
|
+
this.port = credentials.port || 3000;
|
|
12
|
+
this.client = new line.messagingApi.MessagingApiClient({
|
|
13
|
+
channelAccessToken: credentials.accessToken
|
|
14
|
+
});
|
|
15
|
+
this.app = express();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async connect() {
|
|
19
|
+
this.app.post('/callback', line.middleware(this.config), (req, res) => {
|
|
20
|
+
Promise
|
|
21
|
+
.all(req.body.events.map(event => this.handleEvent(event)))
|
|
22
|
+
.then((result) => res.json(result))
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
console.error('[LINE Bridge] Error:', err);
|
|
25
|
+
res.status(500).end();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.server = this.app.listen(this.port, () => {
|
|
30
|
+
console.log(`[LINE Bridge] Listening for webhooks on port ${this.port}`);
|
|
31
|
+
console.log(`[LINE Bridge] Webhook URL should be: <YOUR_PUBLIC_URL>/callback`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async handleEvent(event) {
|
|
36
|
+
if (event.type !== 'message' || event.message.type !== 'text') {
|
|
37
|
+
return Promise.resolve(null);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const result = await handleChat(event.message.text);
|
|
42
|
+
if (result && result.response) {
|
|
43
|
+
return this.client.replyMessage({
|
|
44
|
+
replyToken: event.replyToken,
|
|
45
|
+
messages: [{ type: 'text', text: result.response }],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error('[LINE Bridge] Error processing event:', err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async disconnect() {
|
|
54
|
+
if (this.server) {
|
|
55
|
+
this.server.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = LineBridge;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { App } = require('@slack/bolt');
|
|
2
|
+
const { handleChat } = require('../AI_Brain/Gemini_API');
|
|
3
|
+
|
|
4
|
+
class SlackBridge {
|
|
5
|
+
constructor(credentials) {
|
|
6
|
+
this.app = new App({
|
|
7
|
+
token: credentials.botToken,
|
|
8
|
+
appToken: credentials.appToken,
|
|
9
|
+
socketMode: true
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async connect() {
|
|
14
|
+
this.app.event('app_mention', async ({ event, say }) => {
|
|
15
|
+
try {
|
|
16
|
+
const text = event.text.replace(/<@.*?>/g, '').trim();
|
|
17
|
+
if (!text) return;
|
|
18
|
+
|
|
19
|
+
const result = await handleChat(text);
|
|
20
|
+
if (result && result.response) {
|
|
21
|
+
await say(result.response);
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error('[Slack Bridge] Error processing app_mention:', err);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
this.app.event('message', async ({ event, say }) => {
|
|
29
|
+
// Only respond in DMs
|
|
30
|
+
if (event.channel_type === 'im') {
|
|
31
|
+
try {
|
|
32
|
+
const result = await handleChat(event.text);
|
|
33
|
+
if (result && result.response) {
|
|
34
|
+
await say(result.response);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('[Slack Bridge] Error processing message:', err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await this.app.start();
|
|
43
|
+
console.log('[Slack Bridge] App started in Socket Mode!');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async disconnect() {
|
|
47
|
+
if (this.app) {
|
|
48
|
+
await this.app.stop();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = SlackBridge;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { Telegraf } = require('telegraf');
|
|
2
|
+
const { handleChat } = require('../AI_Brain/Gemini_API');
|
|
3
|
+
|
|
4
|
+
class TelegramBridge {
|
|
5
|
+
constructor(token) {
|
|
6
|
+
this.token = token;
|
|
7
|
+
this.bot = new Telegraf(token);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async connect() {
|
|
11
|
+
this.bot.start((ctx) => ctx.reply('สวัสดีค่ะ! มิ้นท์พร้อมช่วยเหลือคุณใน Telegram แล้วนะคะ ✨'));
|
|
12
|
+
|
|
13
|
+
this.bot.on('text', async (ctx) => {
|
|
14
|
+
try {
|
|
15
|
+
// Show typing status
|
|
16
|
+
await ctx.sendChatAction('typing');
|
|
17
|
+
|
|
18
|
+
const message = ctx.message.text;
|
|
19
|
+
if (!message) return;
|
|
20
|
+
|
|
21
|
+
// Send to Mint AI Brain
|
|
22
|
+
const result = await handleChat(message);
|
|
23
|
+
|
|
24
|
+
// Reply to user
|
|
25
|
+
if (result && result.response) {
|
|
26
|
+
await ctx.reply(result.response);
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('[Telegram Bridge] Error processing message:', err);
|
|
30
|
+
await ctx.reply('ขออภัยค่ะ เกิดข้อผิดพลาดบางอย่างในการประมวลผลข้อความ');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.bot.launch();
|
|
35
|
+
console.log('[Telegram Bridge] Bot started!');
|
|
36
|
+
|
|
37
|
+
// Enable graceful stop
|
|
38
|
+
process.once('SIGINT', () => this.bot.stop('SIGINT'));
|
|
39
|
+
process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async disconnect() {
|
|
43
|
+
if (this.bot) {
|
|
44
|
+
await this.bot.stop();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = TelegramBridge;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { Client, LocalAuth } = require('whatsapp-web.js');
|
|
2
|
+
const qrcode = require('qrcode-terminal');
|
|
3
|
+
const { handleChat } = require('../AI_Brain/Gemini_API');
|
|
4
|
+
|
|
5
|
+
class WhatsappBridge {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.client = new Client({
|
|
8
|
+
authStrategy: new LocalAuth({
|
|
9
|
+
dataPath: require('path').join(require('os').homedir(), '.config', 'mint', 'whatsapp-session')
|
|
10
|
+
}),
|
|
11
|
+
puppeteer: {
|
|
12
|
+
args: ['--no-sandbox']
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect() {
|
|
18
|
+
this.client.on('qr', (qr) => {
|
|
19
|
+
console.log('[WhatsApp Bridge] Scan this QR code to login:');
|
|
20
|
+
qrcode.generate(qr, { small: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.client.on('ready', () => {
|
|
24
|
+
console.log('[WhatsApp Bridge] Client is ready!');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.client.on('message', async (msg) => {
|
|
28
|
+
try {
|
|
29
|
+
// Ignore messages from groups unless mentioned (simple implementation)
|
|
30
|
+
const chat = await msg.getChat();
|
|
31
|
+
if (chat.isGroup) {
|
|
32
|
+
// For groups, we could add a mention check here if desired
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await handleChat(msg.body);
|
|
37
|
+
if (result && result.response) {
|
|
38
|
+
await msg.reply(result.response);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('[WhatsApp Bridge] Error processing message:', err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await this.client.initialize();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async disconnect() {
|
|
49
|
+
if (this.client) {
|
|
50
|
+
await this.client.destroy();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = WhatsappBridge;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
function parseCommand(aiResponse) {
|
|
2
2
|
let action = { type: 'none', target: '' };
|
|
3
3
|
let responseText = '';
|
|
4
|
+
let timestamp = null;
|
|
5
|
+
let providerInfo = null;
|
|
4
6
|
|
|
5
7
|
if (typeof aiResponse === 'string') {
|
|
6
8
|
// Attempt to parse string to JSON
|
|
@@ -8,6 +10,8 @@ function parseCommand(aiResponse) {
|
|
|
8
10
|
const parsed = JSON.parse(aiResponse);
|
|
9
11
|
action = parsed.action || action;
|
|
10
12
|
responseText = parsed.response || '';
|
|
13
|
+
timestamp = parsed.timestamp || null;
|
|
14
|
+
providerInfo = parsed.providerInfo || null;
|
|
11
15
|
} catch (e) {
|
|
12
16
|
// Fallback for markdown
|
|
13
17
|
const jsonMatch = aiResponse.match(/```json\n([\s\S]*?)\n```/) || aiResponse.match(/\{[\s\S]*\}/);
|
|
@@ -16,6 +20,8 @@ function parseCommand(aiResponse) {
|
|
|
16
20
|
const parsed = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
|
|
17
21
|
action = parsed.action || action;
|
|
18
22
|
responseText = parsed.response || '';
|
|
23
|
+
timestamp = parsed.timestamp || null;
|
|
24
|
+
providerInfo = parsed.providerInfo || null;
|
|
19
25
|
} catch (err) {
|
|
20
26
|
responseText = aiResponse;
|
|
21
27
|
}
|
|
@@ -26,9 +32,14 @@ function parseCommand(aiResponse) {
|
|
|
26
32
|
} else if (typeof aiResponse === 'object') {
|
|
27
33
|
action = aiResponse.action || action;
|
|
28
34
|
responseText = aiResponse.response || '';
|
|
35
|
+
timestamp = aiResponse.timestamp || null;
|
|
36
|
+
providerInfo = aiResponse.providerInfo || null;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
const parsedResponse = { response: responseText, action };
|
|
40
|
+
if (timestamp) parsedResponse.timestamp = timestamp;
|
|
41
|
+
if (providerInfo) parsedResponse.providerInfo = providerInfo;
|
|
42
|
+
return parsedResponse;
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
module.exports = { parseCommand };
|