@pheem49/mint 1.4.2 → 1.5.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 (97) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +267 -78
  3. package/assets/CLI_Screen.png +0 -0
  4. package/main.js +76 -890
  5. package/mint-cli-logic.js +3 -107
  6. package/mint-cli.js +594 -29
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
  10. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  24. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  25. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  26. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
  27. package/package.json +37 -4
  28. package/src/AI_Brain/Gemini_API.js +223 -65
  29. package/src/AI_Brain/autonomous_brain.js +11 -0
  30. package/src/AI_Brain/behavior_memory.js +26 -5
  31. package/src/AI_Brain/headless_agent.js +4 -0
  32. package/src/AI_Brain/knowledge_base.js +61 -8
  33. package/src/AI_Brain/memory_store.js +354 -10
  34. package/src/Automation_Layer/file_operations.js +1 -1
  35. package/src/CLI/chat_router.js +20 -7
  36. package/src/CLI/chat_ui.js +596 -825
  37. package/src/CLI/code_agent.js +347 -56
  38. package/src/CLI/gmail_auth.js +210 -0
  39. package/src/CLI/image_input.js +90 -0
  40. package/src/CLI/list_features.js +2 -0
  41. package/src/CLI/onboarding.js +364 -55
  42. package/src/CLI/updater.js +210 -0
  43. package/src/Channels/brave_search_bridge.js +35 -0
  44. package/src/Channels/discord_bridge.js +68 -0
  45. package/src/Channels/google_search_bridge.js +38 -0
  46. package/src/Channels/line_bridge.js +60 -0
  47. package/src/Channels/slack_bridge.js +53 -0
  48. package/src/Channels/telegram_bridge.js +49 -0
  49. package/src/Channels/whatsapp_bridge.js +55 -0
  50. package/src/Command_Parser/parser.js +12 -1
  51. package/src/Plugins/gmail.js +251 -0
  52. package/src/Plugins/google_calendar.js +245 -19
  53. package/src/Plugins/notion.js +256 -0
  54. package/src/System/action_executor.js +178 -0
  55. package/src/System/bridge_manager.js +76 -0
  56. package/src/System/chat_history_manager.js +23 -5
  57. package/src/System/config_manager.js +71 -7
  58. package/src/System/custom_workflows.js +31 -2
  59. package/src/System/google_tts_urls.js +51 -0
  60. package/src/System/granular_automation.js +122 -53
  61. package/src/System/ipc_handlers.js +238 -0
  62. package/src/System/proactive_loop.js +153 -0
  63. package/src/System/safety_manager.js +273 -0
  64. package/src/System/sandbox_runner.js +182 -0
  65. package/src/System/screen_capture.js +175 -0
  66. package/src/System/system_automation.js +127 -81
  67. package/src/System/system_info.js +70 -0
  68. package/src/System/task_manager.js +15 -5
  69. package/src/System/tool_registry.js +280 -0
  70. package/src/System/window_manager.js +212 -0
  71. package/src/UI/live2d_manager.js +368 -0
  72. package/src/UI/renderer.js +208 -24
  73. package/src/UI/settings.html +24 -0
  74. package/src/UI/settings.js +14 -4
  75. package/src/UI/styles.css +466 -32
  76. package/.codex +0 -0
  77. package/docs/assets/Agent_Mint.png +0 -0
  78. package/docs/assets/CLI_Screen.png +0 -0
  79. package/docs/assets/Settings.png +0 -0
  80. package/docs/assets/icon.png +0 -0
  81. package/docs/index.html +0 -132
  82. package/docs/style.css +0 -579
  83. package/index.html +0 -16
  84. package/src/UI/index.html +0 -126
  85. package/tech_news.txt +0 -3
  86. package/test_knowledge.txt +0 -3
  87. package/tests/agent_orchestrator.test.js +0 -41
  88. package/tests/chat_router.test.js +0 -42
  89. package/tests/code_agent.test.js +0 -69
  90. package/tests/config_manager.test.js +0 -141
  91. package/tests/docker.test.js +0 -46
  92. package/tests/file_operations.test.js +0 -57
  93. package/tests/memory_store.test.js +0 -185
  94. package/tests/provider_routing.test.js +0 -67
  95. package/tests/spotify.test.js +0 -201
  96. package/tests/system_monitor.test.js +0 -37
  97. package/tests/workspace_manager.test.js +0 -56
@@ -0,0 +1,178 @@
1
+ let electronClipboard = null;
2
+ try {
3
+ ({ clipboard: electronClipboard } = require('electron'));
4
+ } catch (_) {
5
+ electronClipboard = {
6
+ writeText: () => {}
7
+ };
8
+ }
9
+ const { openApp } = require('../Automation_Layer/open_app');
10
+ const { openWebsite, openSearch } = require('../Automation_Layer/open_website');
11
+ const { performWebAutomation } = require('../Automation_Layer/browser_automation');
12
+ const { createFolder, openFile, deleteFile, findPath } = require('../Automation_Layer/file_operations');
13
+ const { indexFile, indexFolder } = require('../AI_Brain/knowledge_base');
14
+ const { getSystemInfo, getWeather } = require('./system_info');
15
+ const pluginManager = require('../Plugins/plugin_manager');
16
+ const mcpManager = require('../Plugins/mcp_manager');
17
+ const SystemAutomation = require('./system_automation');
18
+ const safetyManager = require('./safety_manager');
19
+ const toolRegistry = require('./tool_registry');
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ async function executeAction(action, options = {}) {
24
+ if (process.env.MINT_DEBUG === '1') {
25
+ console.log("Executing action:", action);
26
+ }
27
+ toolRegistry.validateToolInput(action.type, action);
28
+ const clipboard = options.clipboard || electronClipboard;
29
+ const safety = safetyManager.assertActionAllowed(action, {
30
+ allowApproval: options.allowApproval === true,
31
+ allowDangerous: options.allowDangerous === true
32
+ });
33
+ safetyManager.appendActionLog({
34
+ source: options.source || 'action_executor',
35
+ action: action.type,
36
+ target: action.target || action.path || '',
37
+ tier: safety.tier,
38
+ approved: options.allowApproval === true || options.allowDangerous === true || safety.tier === safetyManager.TIERS.SAFE
39
+ });
40
+
41
+ switch (action.type) {
42
+ case 'open_url':
43
+ openWebsite(action.target);
44
+ break;
45
+ case 'search':
46
+ openSearch(action.target);
47
+ break;
48
+ case 'open_app':
49
+ openApp(action.target);
50
+ break;
51
+ case 'web_automation':
52
+ return await performWebAutomation(action.target);
53
+ case 'create_folder':
54
+ safetyManager.assertPathCapability(action.target, 'write', {
55
+ defaultBase: path.join(os.homedir(), 'Desktop')
56
+ });
57
+ createFolder(action.target);
58
+ break;
59
+ case 'open_file': {
60
+ safetyManager.assertPathCapability(action.target, 'read');
61
+ const fileRes = await openFile(action.target);
62
+ return fileRes || `Successfully opened file: ${action.target} ✅`;
63
+ }
64
+ case 'open_folder': {
65
+ safetyManager.assertPathCapability(action.target, 'read');
66
+ const folderRes = await openFile(action.target);
67
+ return folderRes || `Successfully opened folder: ${action.target} ✅`;
68
+ }
69
+ case 'delete_file':
70
+ safetyManager.assertPathCapability(action.target, 'write');
71
+ await deleteFile(action.target);
72
+ break;
73
+ case 'find_path':
74
+ return await executeFindPath(action);
75
+ case 'clipboard_write':
76
+ clipboard.writeText(action.target);
77
+ break;
78
+ case 'learn_file':
79
+ safetyManager.assertPathCapability(action.target, 'read');
80
+ return await indexFile(action.target);
81
+ case 'learn_folder':
82
+ safetyManager.assertPathCapability(action.target, 'read');
83
+ return await indexFolder(action.target);
84
+ case 'system_info':
85
+ return await handleSystemInfo(action.target);
86
+ case 'mcp_tool': {
87
+ const mcpResult = await mcpManager.callTool(action.server, action.target, action.args);
88
+ return JSON.stringify(mcpResult.content);
89
+ }
90
+ case 'mouse_move': {
91
+ const granularAutomation = require('./granular_automation');
92
+ return await granularAutomation.mouseMove(action.x, action.y);
93
+ }
94
+ case 'mouse_click': {
95
+ const granularAutomation = require('./granular_automation');
96
+ return await granularAutomation.mouseClick(action.x, action.y, action.button || 1);
97
+ }
98
+ case 'type_text': {
99
+ const granularAutomation = require('./granular_automation');
100
+ return await granularAutomation.typeText(action.target);
101
+ }
102
+ case 'key_tap': {
103
+ const granularAutomation = require('./granular_automation');
104
+ return await granularAutomation.keyTap(action.target);
105
+ }
106
+ case 'plugin':
107
+ return await pluginManager.executePlugin(action.pluginName, action.target);
108
+ case 'system_automation':
109
+ return await handleSystemAutomation(action.target);
110
+ default:
111
+ return undefined;
112
+ }
113
+ }
114
+
115
+ async function handleSystemInfo(target = '') {
116
+ const query = String(target || '').trim();
117
+ if (query) {
118
+ const weather = await getWeather(query);
119
+ return JSON.stringify({
120
+ type: 'weather',
121
+ target: query,
122
+ ...weather
123
+ });
124
+ }
125
+ return JSON.stringify({
126
+ type: 'system_info',
127
+ data: getSystemInfo()
128
+ });
129
+ }
130
+
131
+ async function executeFindPath(action) {
132
+ const result = findPath(action.target, {
133
+ type: action.pathType,
134
+ maxResults: 10,
135
+ roots: safetyManager.getAllowedRoots('read')
136
+ });
137
+ if (!result.success) {
138
+ return result.message;
139
+ }
140
+
141
+ if (action.openAfter === true) {
142
+ if (result.matches.length === 1) {
143
+ const match = result.matches[0];
144
+ const openResult = await openFile(match.path);
145
+ return openResult || `Successfully found and opened ${match.type === 'dir' ? 'folder' : 'file'}: ${match.path} ✅`;
146
+ }
147
+ return `Found multiple matches for "${action.target}". Please be more specific:\n${result.matches.map(m => `- [${m.type}] ${m.path}`).join('\n')}`;
148
+ }
149
+
150
+ return `Found matches for "${action.target}":\n${result.matches.map(m => `- [${m.type}] ${m.path}`).join('\n')}`;
151
+ }
152
+
153
+ async function handleSystemAutomation(target) {
154
+ const [cmd, value] = target.split(':');
155
+ switch (cmd) {
156
+ case 'volume':
157
+ return await SystemAutomation.setVolume(parseInt(value));
158
+ case 'mute':
159
+ return await SystemAutomation.mute();
160
+ case 'brightness':
161
+ return await SystemAutomation.setBrightness(parseInt(value));
162
+ case 'sleep':
163
+ return await SystemAutomation.sleep();
164
+ case 'restart':
165
+ return await SystemAutomation.restart();
166
+ case 'shutdown':
167
+ return await SystemAutomation.shutdown();
168
+ case 'minimize_all':
169
+ return await SystemAutomation.minimizeAll();
170
+ default:
171
+ if (SystemAutomation[target]) {
172
+ return await SystemAutomation[target]();
173
+ }
174
+ throw new Error(`Unknown system automation command: ${target}`);
175
+ }
176
+ }
177
+
178
+ module.exports = { executeAction, handleSystemAutomation, handleSystemInfo };
@@ -0,0 +1,76 @@
1
+ const { readConfig } = require('./config_manager');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ class BridgeManager {
6
+ constructor() {
7
+ this.bridges = new Map();
8
+ this.channelsDir = path.join(__dirname, '..', 'Channels');
9
+
10
+ if (!fs.existsSync(this.channelsDir)) {
11
+ fs.mkdirSync(this.channelsDir, { recursive: true });
12
+ }
13
+ }
14
+
15
+ async init() {
16
+ const config = readConfig();
17
+ console.log('[BridgeManager] Initializing messaging bridges...');
18
+
19
+ // Load Discord Bridge
20
+ if (config.enableDiscordBridge && config.discordBotToken) {
21
+ await this.startBridge('discord', config.discordBotToken);
22
+ }
23
+
24
+ // Load Telegram Bridge
25
+ if (config.enableTelegramBridge && config.telegramBotToken) {
26
+ await this.startBridge('telegram', config.telegramBotToken);
27
+ }
28
+
29
+ // Load Slack Bridge
30
+ if (config.enableSlackBridge && config.slackBotToken && config.slackAppToken) {
31
+ await this.startBridge('slack', { botToken: config.slackBotToken, appToken: config.slackAppToken });
32
+ }
33
+
34
+ // Load LINE Bridge
35
+ if (config.enableLineBridge && config.lineChannelAccessToken && config.lineChannelSecret) {
36
+ await this.startBridge('line', { accessToken: config.lineChannelAccessToken, secret: config.lineChannelSecret, port: config.lineWebhookPort });
37
+ }
38
+
39
+ // Load WhatsApp Bridge
40
+ if (config.enableWhatsappBridge) {
41
+ await this.startBridge('whatsapp', null);
42
+ }
43
+ }
44
+
45
+ async startBridge(type, credentials) {
46
+ try {
47
+ const bridgePath = path.join(this.channelsDir, `${type}_bridge.js`);
48
+ if (!fs.existsSync(bridgePath)) {
49
+ console.error(`[BridgeManager] Bridge file not found: ${bridgePath}`);
50
+ return;
51
+ }
52
+
53
+ const BridgeClass = require(bridgePath);
54
+ const bridge = new BridgeClass(credentials);
55
+ await bridge.connect();
56
+ this.bridges.set(type, bridge);
57
+ console.log(`[BridgeManager] ${type.toUpperCase()} bridge connected successfully.`);
58
+ } catch (err) {
59
+ console.error(`[BridgeManager] Failed to start ${type} bridge:`, err.message);
60
+ }
61
+ }
62
+
63
+ async shutdown() {
64
+ for (const [type, bridge] of this.bridges.entries()) {
65
+ try {
66
+ await bridge.disconnect();
67
+ console.log(`[BridgeManager] ${type.toUpperCase()} bridge disconnected.`);
68
+ } catch (err) {
69
+ console.error(`[BridgeManager] Error disconnecting ${type} bridge:`, err.message);
70
+ }
71
+ }
72
+ this.bridges.clear();
73
+ }
74
+ }
75
+
76
+ module.exports = new BridgeManager();
@@ -10,14 +10,32 @@ try {
10
10
  app = null;
11
11
  }
12
12
 
13
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'mint');
13
14
  const MINT_DIR = path.join(os.homedir(), '.mint');
14
- if (!fs.existsSync(MINT_DIR)) {
15
- fs.mkdirSync(MINT_DIR, { recursive: true });
15
+
16
+ if (!fs.existsSync(CONFIG_DIR)) {
17
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
16
18
  }
17
19
 
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');
20
+ const CHAT_HISTORY_PATH = path.join(CONFIG_DIR, 'mint-chat-history.json');
21
+
22
+ // Migration Logic: Consolidate from Electron userData or old ~/.mint to ~/.config/mint
23
+ if (!fs.existsSync(CHAT_HISTORY_PATH)) {
24
+ const electronUserData = app && app.getPath ? path.join(app.getPath('userData'), 'mint-chat-history.json') : null;
25
+ const legacyPath = path.join(MINT_DIR, 'mint-chat-history.json');
26
+
27
+ if (electronUserData && fs.existsSync(electronUserData)) {
28
+ try {
29
+ fs.copyFileSync(electronUserData, CHAT_HISTORY_PATH);
30
+ console.log('[History] Migrated chat history from Electron userData');
31
+ } catch (e) { console.error('[History] Migration from Electron failed:', e); }
32
+ } else if (fs.existsSync(legacyPath)) {
33
+ try {
34
+ fs.copyFileSync(legacyPath, CHAT_HISTORY_PATH);
35
+ console.log('[History] Migrated chat history from ~/.mint');
36
+ } catch (e) { console.error('[History] Migration from ~/.mint failed:', e); }
37
+ }
38
+ }
21
39
 
22
40
  function readChatHistory() {
23
41
  try {
@@ -53,21 +53,83 @@ const DEFAULT_CONFIG = {
53
53
  ttsVolume: 1.0,
54
54
  ttsSpeed: 1.0,
55
55
  ttsPitch: 1.0,
56
- pluginSpotifyEnabled: true,
57
56
  pluginCalendarEnabled: false,
57
+ pluginGmailEnabled: false,
58
+ pluginNotionEnabled: false,
58
59
  pluginDiscordEnabled: false,
59
60
  showDesktopWidget: true,
60
61
  mcpServers: {},
62
+ telegramBotToken: '',
63
+ enableTelegramBridge: false,
64
+ discordBotToken: '',
65
+ enableDiscordBridge: false,
66
+ slackBotToken: '',
67
+ slackAppToken: '',
68
+ enableSlackBridge: false,
69
+ lineChannelAccessToken: '',
70
+ lineChannelSecret: '',
71
+ enableLineBridge: false,
72
+ lineWebhookPort: 3000,
73
+ enableWhatsappBridge: false,
74
+ googleSearchApiKey: '',
75
+ googleSearchCx: '',
76
+ googleCalendarClientId: '',
77
+ googleCalendarClientSecret: '',
78
+ googleCalendarRefreshToken: '',
79
+ googleCalendarId: 'primary',
80
+ gmailClientId: '',
81
+ gmailClientSecret: '',
82
+ gmailRefreshToken: '',
83
+ gmailUserId: 'me',
84
+ notionApiKey: '',
85
+ notionDatabaseId: '',
86
+ notionPageId: '',
87
+ notionTitleProperty: 'Name',
88
+ braveSearchApiKey: '',
61
89
  anthropicApiKey: '',
90
+
62
91
  openaiApiKey: '',
63
92
  hfApiKey: '',
64
93
  anthropicModel: 'claude-3-5-sonnet-latest',
65
94
  openaiModel: 'gpt-4o',
66
95
  hfModel: 'meta-llama/Meta-Llama-3-8B-Instruct',
67
- localApiBaseUrl: 'http://localhost:1234/v1',
96
+ localApiBaseUrl: '',
68
97
  localModelName: 'local-model',
69
- ollamaHost: 'http://localhost:11434',
70
- enableAgentCollaboration: false
98
+ ollamaHost: '',
99
+ enableAgentCollaboration: false,
100
+ enableAutoUpdate: true,
101
+ autoUpdateCheckIntervalHours: 24,
102
+ lastUpdateCheckAt: '',
103
+ safetyEnabled: true,
104
+ sandboxMode: 'prefer', // off | prefer | enforce
105
+ sandboxCommand: process.platform === 'darwin' ? 'sandbox-exec' : process.platform === 'linux' ? 'bwrap' : '',
106
+ allowedReadPaths: [
107
+ os.homedir(),
108
+ process.cwd(),
109
+ path.join(os.homedir(), 'Desktop'),
110
+ path.join(os.homedir(), 'Documents'),
111
+ path.join(os.homedir(), 'Downloads'),
112
+ path.join(os.homedir(), 'Pictures'),
113
+ path.join(os.homedir(), 'Music'),
114
+ path.join(os.homedir(), 'Videos')
115
+ ],
116
+ allowedWritePaths: [
117
+ os.homedir(),
118
+ process.cwd(),
119
+ path.join(os.homedir(), 'Desktop'),
120
+ path.join(os.homedir(), 'Documents'),
121
+ path.join(os.homedir(), 'Downloads'),
122
+ path.join(os.homedir(), 'Pictures'),
123
+ path.join(os.homedir(), 'Music'),
124
+ path.join(os.homedir(), 'Videos')
125
+ ],
126
+ blockedPaths: [
127
+ path.join(os.homedir(), '.ssh'),
128
+ path.join(os.homedir(), '.gnupg'),
129
+ path.join(os.homedir(), '.config', 'mint', 'mint-config.json'),
130
+ path.join(os.homedir(), '.mint', 'mint-config.json')
131
+ ],
132
+ blockedFileNames: ['.env', 'id_rsa', 'id_ed25519']
71
133
  };
72
134
 
73
135
 
@@ -100,8 +162,6 @@ function getAvailableProviders(config) {
100
162
  const providers = [];
101
163
  const cfg = config || readConfig();
102
164
 
103
- const isPlaceholder = (val) => !val || val.startsWith('your_') || val.includes('key_here') || val.trim() === '';
104
-
105
165
  // Check which providers have API keys or URLs configured
106
166
  const anthropicKey = cfg.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
107
167
  if (!isPlaceholder(anthropicKey)) providers.push('anthropic');
@@ -123,4 +183,8 @@ function getAvailableProviders(config) {
123
183
  return providers;
124
184
  }
125
185
 
126
- module.exports = { readConfig, writeConfig, getAvailableProviders, CONFIG_PATH };
186
+ function isPlaceholder(val) {
187
+ return !val || val.startsWith('your_') || val.includes('key_here') || val.trim() === '';
188
+ }
189
+
190
+ module.exports = { readConfig, writeConfig, getAvailableProviders, isPlaceholder, CONFIG_PATH };
@@ -1,15 +1,27 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { app, shell } = require('electron');
3
+ const os = require('os');
4
4
  const { exec } = require('child_process');
5
5
 
6
+ // Handle electron dependency safely
7
+ let app, shell;
8
+ try {
9
+ const electron = require('electron');
10
+ app = electron.app;
11
+ shell = electron.shell;
12
+ } catch (e) {
13
+ app = null;
14
+ shell = null;
15
+ }
16
+
6
17
  function escapeRegExp(text) {
7
18
  return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8
19
  }
9
20
 
10
21
  class CustomWorkflows {
11
22
  constructor() {
12
- this.configPath = path.join(app.getPath('userData'), 'workflows.json');
23
+ const configDir = path.join(os.homedir(), '.config', 'mint');
24
+ this.configPath = path.join(configDir, 'workflows.json');
13
25
  this.workflows = [];
14
26
  this.lastTriggered = {};
15
27
  this.cooldownMs = 60 * 60 * 1000; // 1 hour cooldown per rule
@@ -17,10 +29,27 @@ class CustomWorkflows {
17
29
  this.timer = null;
18
30
  this.webContents = null;
19
31
 
32
+ if (!fs.existsSync(configDir)) {
33
+ fs.mkdirSync(configDir, { recursive: true });
34
+ }
35
+
36
+ this.migrateConfig();
20
37
  this.ensureConfigExists();
21
38
  this.loadWorkflows();
22
39
  }
23
40
 
41
+ migrateConfig() {
42
+ if (!fs.existsSync(this.configPath) && app && app.getPath) {
43
+ const electronPath = path.join(app.getPath('userData'), 'workflows.json');
44
+ if (fs.existsSync(electronPath)) {
45
+ try {
46
+ fs.copyFileSync(electronPath, this.configPath);
47
+ console.log('[CustomWorkflows] Migrated workflows from Electron userData');
48
+ } catch (e) { console.error('[CustomWorkflows] Migration failed:', e); }
49
+ }
50
+ }
51
+ }
52
+
24
53
  ensureConfigExists() {
25
54
  if (!fs.existsSync(this.configPath)) {
26
55
  const defaultWorkflows = [
@@ -0,0 +1,51 @@
1
+ const MAX_GOOGLE_TTS_CHARS = 200;
2
+
3
+ function splitTextForTts(text, maxLength = MAX_GOOGLE_TTS_CHARS) {
4
+ const normalized = String(text || '').replace(/\s+/g, ' ').trim();
5
+ if (!normalized) return [];
6
+
7
+ const chunks = [];
8
+ let remaining = normalized;
9
+
10
+ while (remaining.length > maxLength) {
11
+ const slice = remaining.slice(0, maxLength + 1);
12
+ const splitAt = Math.max(
13
+ slice.lastIndexOf('.'),
14
+ slice.lastIndexOf('?'),
15
+ slice.lastIndexOf('!'),
16
+ slice.lastIndexOf(','),
17
+ slice.lastIndexOf(' ')
18
+ );
19
+ const safeSplit = splitAt > 0 ? splitAt : maxLength;
20
+ chunks.push(remaining.slice(0, safeSplit).trim());
21
+ remaining = remaining.slice(safeSplit).trim();
22
+ }
23
+
24
+ if (remaining) chunks.push(remaining);
25
+ return chunks;
26
+ }
27
+
28
+ function getGoogleTtsUrls(text, options = {}) {
29
+ const lang = options.lang || 'en';
30
+ const host = options.host || 'https://translate.google.com';
31
+ const chunks = splitTextForTts(text);
32
+
33
+ return chunks.map((chunk, index) => {
34
+ const params = new URLSearchParams({
35
+ ie: 'UTF-8',
36
+ q: chunk,
37
+ tl: lang,
38
+ client: 'tw-ob',
39
+ idx: String(index),
40
+ total: String(chunks.length),
41
+ textlen: String(chunk.length)
42
+ });
43
+
44
+ return {
45
+ shortText: chunk,
46
+ url: `${host}/translate_tts?${params.toString()}`
47
+ };
48
+ });
49
+ }
50
+
51
+ module.exports = { getGoogleTtsUrls, splitTextForTts };