@pheem49/mint 1.5.5 → 1.6.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 (222) hide show
  1. package/.codex +0 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/.github/workflows/ci.yml +45 -0
  4. package/.github/workflows/release.yml +79 -0
  5. package/Cargo.lock +5792 -0
  6. package/Cargo.toml +32 -0
  7. package/README.md +387 -353
  8. package/assets/icon.png +0 -0
  9. package/bin/mint +0 -0
  10. package/crates/mint-cli/Cargo.toml +23 -0
  11. package/crates/mint-cli/src/agent.rs +851 -0
  12. package/crates/mint-cli/src/gmail.rs +216 -0
  13. package/crates/mint-cli/src/image.rs +142 -0
  14. package/crates/mint-cli/src/main.rs +2837 -0
  15. package/crates/mint-cli/src/mcp.rs +63 -0
  16. package/crates/mint-cli/src/onboard.rs +1149 -0
  17. package/crates/mint-cli/src/setup.rs +390 -0
  18. package/crates/mint-cli/src/skills.rs +8 -0
  19. package/crates/mint-cli/src/updater.rs +279 -0
  20. package/crates/mint-core/Cargo.toml +22 -0
  21. package/crates/mint-core/src/agent_loop.rs +94 -0
  22. package/crates/mint-core/src/api_server.rs +991 -0
  23. package/crates/mint-core/src/channels.rs +248 -0
  24. package/crates/mint-core/src/chat.rs +895 -0
  25. package/crates/mint-core/src/code_tools.rs +729 -0
  26. package/crates/mint-core/src/config.rs +368 -0
  27. package/crates/mint-core/src/files.rs +159 -0
  28. package/crates/mint-core/src/knowledge.rs +541 -0
  29. package/crates/mint-core/src/lib.rs +84 -0
  30. package/crates/mint-core/src/mcp.rs +273 -0
  31. package/crates/mint-core/src/memory.rs +673 -0
  32. package/crates/mint-core/src/orchestration.rs +2157 -0
  33. package/crates/mint-core/src/pictures.rs +314 -0
  34. package/crates/mint-core/src/plugins.rs +727 -0
  35. package/crates/mint-core/src/safety.rs +416 -0
  36. package/crates/mint-core/src/semantic.rs +254 -0
  37. package/crates/mint-core/src/shell.rs +317 -0
  38. package/crates/mint-core/src/skills.rs +71 -0
  39. package/crates/mint-core/src/symbols.rs +157 -0
  40. package/crates/mint-core/src/tasks.rs +308 -0
  41. package/crates/mint-core/src/tts.rs +92 -0
  42. package/crates/mint-core/src/weather.rs +93 -0
  43. package/crates/mint-core/src/web_search.rs +200 -0
  44. package/crates/mint-core/src/workflows.rs +81 -0
  45. package/crates/mint-core/tests/mcp_stdio.rs +45 -0
  46. package/crates/mint-core/tests/memory_persistence.rs +172 -0
  47. package/crates/mint-core/tests/pictures_storage.rs +14 -0
  48. package/crates/mint-core/tests/task_lifecycle.rs +87 -0
  49. package/package.json +35 -99
  50. package/src/bin/index.js +16 -0
  51. package/src/renderer/index-web.html +17 -0
  52. package/src/renderer/index.html +17 -0
  53. package/src/renderer/public/Live2DCubismCore.js +9 -0
  54. package/src/renderer/public/assets/icon.png +0 -0
  55. package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
  56. package/src/renderer/src/App.tsx +33 -0
  57. package/src/renderer/src/calculator.ts +47 -0
  58. package/src/renderer/src/components/ChatPanel.tsx +1598 -0
  59. package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
  60. package/src/renderer/src/components/Live2DStage.tsx +374 -0
  61. package/src/renderer/src/components/MintDashboard.tsx +950 -0
  62. package/src/renderer/src/components/ModelPanel.tsx +154 -0
  63. package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
  64. package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
  65. package/src/renderer/src/components/ScreenPicker.tsx +579 -0
  66. package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
  67. package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
  68. package/src/renderer/src/components/WidgetWindow.tsx +36 -0
  69. package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
  70. package/src/{UI → renderer/src/css}/settings.css +69 -16
  71. package/src/renderer/src/css/spotlight.css +113 -0
  72. package/src/renderer/src/css/styles.css +3722 -0
  73. package/src/renderer/src/css/widget.css +185 -0
  74. package/src/renderer/src/env.d.ts +116 -0
  75. package/src/renderer/src/index.css +379 -0
  76. package/src/renderer/src/main.tsx +13 -0
  77. package/src/renderer/src/tauri.ts +996 -0
  78. package/src/renderer/src-web/App.tsx +25 -0
  79. package/src/renderer/src-web/calculator.ts +47 -0
  80. package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
  81. package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
  82. package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
  83. package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
  84. package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
  85. package/src/renderer/src-web/css/settings.css +1100 -0
  86. package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
  87. package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
  88. package/src/{UI → renderer/src-web/css}/widget.css +2 -2
  89. package/src/renderer/src-web/env.d.ts +107 -0
  90. package/src/renderer/src-web/index.css +379 -0
  91. package/src/renderer/src-web/main.tsx +13 -0
  92. package/src/renderer/src-web/tauri.ts +983 -0
  93. package/tsconfig.json +30 -0
  94. package/vite.config.ts +33 -0
  95. package/vite.config.web.ts +51 -0
  96. package/GUIDE_TH.md +0 -125
  97. package/assets/Agent_Mint.png +0 -0
  98. package/assets/CLI_Screen.png +0 -0
  99. package/assets/Settings.png +0 -0
  100. package/benchmark_ai.js +0 -71
  101. package/install.ps1 +0 -64
  102. package/install.sh +0 -54
  103. package/main.js +0 -139
  104. package/mint-cli-logic.js +0 -3
  105. package/mint-cli.js +0 -410
  106. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
  107. 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 +0 -23
  108. package/preload-picker.js +0 -11
  109. package/preload-settings.js +0 -11
  110. package/preload.js +0 -41
  111. package/scripts/install_linux_desktop_entry.js +0 -48
  112. package/src/AI_Brain/Gemini_API.js +0 -813
  113. package/src/AI_Brain/agent_orchestrator.js +0 -73
  114. package/src/AI_Brain/autonomous_brain.js +0 -179
  115. package/src/AI_Brain/behavior_memory.js +0 -135
  116. package/src/AI_Brain/headless_agent.js +0 -143
  117. package/src/AI_Brain/knowledge_base.js +0 -349
  118. package/src/AI_Brain/memory_store.js +0 -662
  119. package/src/AI_Brain/proactive_engine.js +0 -172
  120. package/src/AI_Brain/provider_adapter.js +0 -365
  121. package/src/Automation_Layer/browser_automation.js +0 -149
  122. package/src/Automation_Layer/file_operations.js +0 -286
  123. package/src/Automation_Layer/open_app.js +0 -85
  124. package/src/Automation_Layer/open_website.js +0 -38
  125. package/src/CLI/approval_handler.js +0 -47
  126. package/src/CLI/chat_router.js +0 -247
  127. package/src/CLI/chat_ui.js +0 -1159
  128. package/src/CLI/cli_colors.js +0 -115
  129. package/src/CLI/cli_formatters.js +0 -94
  130. package/src/CLI/code_agent.js +0 -1667
  131. package/src/CLI/code_session_memory.js +0 -62
  132. package/src/CLI/gmail_auth.js +0 -210
  133. package/src/CLI/image_input.js +0 -90
  134. package/src/CLI/intent_detectors.js +0 -181
  135. package/src/CLI/interactive_chat.js +0 -658
  136. package/src/CLI/list_features.js +0 -64
  137. package/src/CLI/onboarding.js +0 -416
  138. package/src/CLI/repo_summarizer.js +0 -282
  139. package/src/CLI/semantic_code_search.js +0 -312
  140. package/src/CLI/skill_manager.js +0 -41
  141. package/src/CLI/slash_command_handler.js +0 -418
  142. package/src/CLI/symbol_indexer.js +0 -231
  143. package/src/CLI/updater.js +0 -230
  144. package/src/CLI/workspace_manager.js +0 -90
  145. package/src/Channels/brave_search_bridge.js +0 -35
  146. package/src/Channels/discord_bridge.js +0 -66
  147. package/src/Channels/google_search_bridge.js +0 -38
  148. package/src/Channels/line_bridge.js +0 -60
  149. package/src/Channels/slack_bridge.js +0 -48
  150. package/src/Channels/telegram_bridge.js +0 -41
  151. package/src/Channels/whatsapp_bridge.js +0 -57
  152. package/src/Command_Parser/parser.js +0 -45
  153. package/src/Plugins/dev_tools.js +0 -41
  154. package/src/Plugins/discord.js +0 -20
  155. package/src/Plugins/docker.js +0 -47
  156. package/src/Plugins/gmail.js +0 -251
  157. package/src/Plugins/google_calendar.js +0 -252
  158. package/src/Plugins/mcp_manager.js +0 -95
  159. package/src/Plugins/notion.js +0 -256
  160. package/src/Plugins/obsidian.js +0 -54
  161. package/src/Plugins/plugin_manager.js +0 -81
  162. package/src/Plugins/spotify.js +0 -173
  163. package/src/Plugins/system_metrics.js +0 -31
  164. package/src/Plugins/system_monitor.js +0 -72
  165. package/src/System/action_executor.js +0 -178
  166. package/src/System/bridge_manager.js +0 -76
  167. package/src/System/chat_history_manager.js +0 -83
  168. package/src/System/config_manager.js +0 -194
  169. package/src/System/custom_workflows.js +0 -163
  170. package/src/System/daemon_manager.js +0 -67
  171. package/src/System/google_tts_urls.js +0 -51
  172. package/src/System/granular_automation.js +0 -157
  173. package/src/System/ipc_handlers.js +0 -332
  174. package/src/System/notifications.js +0 -23
  175. package/src/System/optional_require.js +0 -23
  176. package/src/System/picture_store.js +0 -109
  177. package/src/System/proactive_loop.js +0 -153
  178. package/src/System/safety_manager.js +0 -273
  179. package/src/System/sandbox_runner.js +0 -182
  180. package/src/System/screen_capture.js +0 -175
  181. package/src/System/smart_context.js +0 -227
  182. package/src/System/system_automation.js +0 -162
  183. package/src/System/system_events.js +0 -79
  184. package/src/System/system_info.js +0 -125
  185. package/src/System/task_manager.js +0 -222
  186. package/src/System/tool_registry.js +0 -293
  187. package/src/System/window_manager.js +0 -220
  188. package/src/UI/floating.css +0 -80
  189. package/src/UI/floating.html +0 -17
  190. package/src/UI/floating.js +0 -67
  191. package/src/UI/live2d_manager.js +0 -600
  192. package/src/UI/preload-floating.js +0 -7
  193. package/src/UI/preload-spotlight.js +0 -11
  194. package/src/UI/preload-widget.js +0 -5
  195. package/src/UI/proactive-glow.html +0 -42
  196. package/src/UI/renderer.js +0 -2127
  197. package/src/UI/screenPicker.html +0 -214
  198. package/src/UI/screenPicker.js +0 -262
  199. package/src/UI/settings.html +0 -577
  200. package/src/UI/settings.js +0 -770
  201. package/src/UI/spotlight.html +0 -23
  202. package/src/UI/spotlight.js +0 -185
  203. package/src/UI/widget.html +0 -29
  204. package/src/UI/widget.js +0 -10
  205. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  206. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
  207. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
  208. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
  209. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
  210. /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" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
  211. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
  212. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
  213. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
  214. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
  215. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
  216. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
  217. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
  218. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
  219. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
  220. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
  221. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
  222. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
@@ -1,45 +0,0 @@
1
- function parseCommand(aiResponse) {
2
- let action = { type: 'none', target: '' };
3
- let responseText = '';
4
- let timestamp = null;
5
- let providerInfo = null;
6
-
7
- if (typeof aiResponse === 'string') {
8
- // Attempt to parse string to JSON
9
- try {
10
- const parsed = JSON.parse(aiResponse);
11
- action = parsed.action || action;
12
- responseText = parsed.response || '';
13
- timestamp = parsed.timestamp || null;
14
- providerInfo = parsed.providerInfo || null;
15
- } catch (e) {
16
- // Fallback for markdown
17
- const jsonMatch = aiResponse.match(/```json\n([\s\S]*?)\n```/) || aiResponse.match(/\{[\s\S]*\}/);
18
- if (jsonMatch) {
19
- try {
20
- const parsed = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
21
- action = parsed.action || action;
22
- responseText = parsed.response || '';
23
- timestamp = parsed.timestamp || null;
24
- providerInfo = parsed.providerInfo || null;
25
- } catch (err) {
26
- responseText = aiResponse;
27
- }
28
- } else {
29
- responseText = aiResponse;
30
- }
31
- }
32
- } else if (typeof aiResponse === 'object') {
33
- action = aiResponse.action || action;
34
- responseText = aiResponse.response || '';
35
- timestamp = aiResponse.timestamp || null;
36
- providerInfo = aiResponse.providerInfo || null;
37
- }
38
-
39
- const parsedResponse = { response: responseText, action };
40
- if (timestamp) parsedResponse.timestamp = timestamp;
41
- if (providerInfo) parsedResponse.providerInfo = providerInfo;
42
- return parsedResponse;
43
- }
44
-
45
- module.exports = { parseCommand };
@@ -1,41 +0,0 @@
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
- };
@@ -1,20 +0,0 @@
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
- };
@@ -1,47 +0,0 @@
1
- const { execFile } = 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 rawTarget = (target || '').trim();
12
- const [rawAction, ...args] = rawTarget.split(/\s+/);
13
- const action = (rawAction || '').toLowerCase();
14
- const containerName = args.join(' ');
15
- let commandArgs = [];
16
-
17
- if (action === 'list') {
18
- commandArgs = ['ps', '--format', '{{.Names}} ({{.Status}})'];
19
- } else if (['start', 'stop', 'restart'].includes(action) && containerName) {
20
- commandArgs = [action, containerName];
21
- } else {
22
- return resolve(`Invalid docker command or missing container name: ${target}`);
23
- }
24
-
25
- execFile('docker', commandArgs, (error, stdout, stderr) => {
26
- if (error) {
27
- const stderrText = stderr || '';
28
- if (error.code === 127 || stderrText.includes('not found') || error.code === 'ENOENT') {
29
- return resolve('Error: Docker is not installed or not in PATH.');
30
- }
31
- if (stderrText.toLowerCase().includes('permission denied')) {
32
- return resolve('Error: Permission denied. You might need to add your user to the "docker" group.');
33
- }
34
- return resolve(`Docker Error: ${stderrText || error.message}`);
35
- }
36
-
37
- if (action === 'list') {
38
- const containers = stdout.trim();
39
- if (!containers) return resolve("No running Docker containers found.");
40
- return resolve(`Running Containers:\n${containers}`);
41
- }
42
-
43
- resolve(`Successfully executed "docker ${action}" on container "${containerName}".`);
44
- });
45
- });
46
- }
47
- };
@@ -1,251 +0,0 @@
1
- const axios = require('axios');
2
- const { readConfig } = require('../System/config_manager');
3
-
4
- const TOKEN_URL = 'https://oauth2.googleapis.com/token';
5
- const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1';
6
-
7
- function hasGmailConfig(config) {
8
- return Boolean(config.gmailClientId && config.gmailClientSecret && config.gmailRefreshToken);
9
- }
10
-
11
- function parseInstruction(instruction) {
12
- const raw = (instruction || '').trim();
13
- if (!raw) return { action: 'help' };
14
-
15
- try {
16
- const parsed = JSON.parse(raw);
17
- if (parsed && typeof parsed === 'object') {
18
- return {
19
- action: normalizeAction(parsed.action || 'search'),
20
- ...parsed
21
- };
22
- }
23
- } catch {
24
- // Plain text searches Gmail.
25
- }
26
-
27
- const lower = raw.toLowerCase();
28
- if (lower === 'help') return { action: 'help' };
29
- if (lower === 'unread') return { action: 'search', query: 'is:unread' };
30
- if (lower === 'inbox') return { action: 'search', query: 'in:inbox' };
31
- if (lower.startsWith('read ')) return { action: 'read', id: raw.slice(5).trim() };
32
- if (lower.startsWith('draft ')) return { action: 'draft', body: raw.slice(6).trim() };
33
- if (lower.startsWith('search ')) return { action: 'search', query: raw.slice(7).trim() };
34
-
35
- return { action: 'search', query: raw };
36
- }
37
-
38
- function normalizeAction(action) {
39
- const normalized = String(action || '').toLowerCase();
40
- if (['list', 'search', 'inbox', 'unread'].includes(normalized)) return 'search';
41
- if (['get', 'read', 'read_email', 'message'].includes(normalized)) return 'read';
42
- if (['draft', 'create_draft', 'compose', 'write'].includes(normalized)) return 'draft';
43
- return normalized;
44
- }
45
-
46
- function gmailUserId(config) {
47
- return encodeURIComponent(config.gmailUserId || 'me');
48
- }
49
-
50
- async function getAccessToken(config) {
51
- const params = new URLSearchParams({
52
- client_id: config.gmailClientId,
53
- client_secret: config.gmailClientSecret,
54
- refresh_token: config.gmailRefreshToken,
55
- grant_type: 'refresh_token'
56
- });
57
-
58
- const response = await axios.post(TOKEN_URL, params.toString(), {
59
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
60
- });
61
-
62
- return response.data.access_token;
63
- }
64
-
65
- function gmailHeaders(accessToken) {
66
- return {
67
- Authorization: `Bearer ${accessToken}`,
68
- 'Content-Type': 'application/json'
69
- };
70
- }
71
-
72
- function decodeBase64Url(data = '') {
73
- const normalized = String(data).replace(/-/g, '+').replace(/_/g, '/');
74
- const padded = normalized.padEnd(normalized.length + ((4 - normalized.length % 4) % 4), '=');
75
- return Buffer.from(padded, 'base64').toString('utf8');
76
- }
77
-
78
- function encodeBase64Url(data = '') {
79
- return Buffer.from(String(data), 'utf8')
80
- .toString('base64')
81
- .replace(/\+/g, '-')
82
- .replace(/\//g, '_')
83
- .replace(/=+$/g, '');
84
- }
85
-
86
- function getHeader(message, name) {
87
- const headers = message.payload?.headers || [];
88
- const found = headers.find(header => header.name && header.name.toLowerCase() === name.toLowerCase());
89
- return found ? found.value : '';
90
- }
91
-
92
- function findTextPart(payload) {
93
- if (!payload) return '';
94
- if (payload.mimeType === 'text/plain' && payload.body?.data) {
95
- return decodeBase64Url(payload.body.data);
96
- }
97
- if (payload.mimeType === 'text/html' && payload.body?.data) {
98
- return decodeBase64Url(payload.body.data).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
99
- }
100
- for (const part of payload.parts || []) {
101
- const text = findTextPart(part);
102
- if (text) return text;
103
- }
104
- return payload.body?.data ? decodeBase64Url(payload.body.data) : '';
105
- }
106
-
107
- function formatMessageSummary(message) {
108
- const subject = getHeader(message, 'Subject') || '(No subject)';
109
- const from = getHeader(message, 'From') || '(Unknown sender)';
110
- const date = getHeader(message, 'Date');
111
- const snippet = message.snippet || '';
112
- return [
113
- `ID: ${message.id}`,
114
- `From: ${from}`,
115
- `Subject: ${subject}`,
116
- date ? `Date: ${date}` : '',
117
- snippet ? `Snippet: ${snippet}` : ''
118
- ].filter(Boolean).join('\n');
119
- }
120
-
121
- async function fetchMessage(config, accessToken, id, format = 'metadata') {
122
- const response = await axios.get(`${GMAIL_API_BASE}/users/${gmailUserId(config)}/messages/${encodeURIComponent(id)}`, {
123
- headers: gmailHeaders(accessToken),
124
- params: {
125
- format,
126
- metadataHeaders: ['From', 'To', 'Subject', 'Date']
127
- }
128
- });
129
- return response.data;
130
- }
131
-
132
- async function searchMessages(config, input, accessToken) {
133
- const query = input.query || input.q || 'in:inbox';
134
- const maxResults = Number(input.maxResults || input.limit || 10);
135
- const response = await axios.get(`${GMAIL_API_BASE}/users/${gmailUserId(config)}/messages`, {
136
- headers: gmailHeaders(accessToken),
137
- params: {
138
- q: query,
139
- maxResults
140
- }
141
- });
142
-
143
- const messages = response.data.messages || [];
144
- if (messages.length === 0) return `No Gmail messages found for query: ${query}`;
145
-
146
- const detailed = [];
147
- for (const message of messages.slice(0, maxResults)) {
148
- const full = await fetchMessage(config, accessToken, message.id, 'metadata');
149
- detailed.push(formatMessageSummary(full));
150
- }
151
-
152
- return `Gmail search results for "${query}":\n\n${detailed.join('\n\n')}`;
153
- }
154
-
155
- async function readMessage(config, input, accessToken) {
156
- const id = input.id || input.messageId;
157
- if (!id) throw new Error('Missing Gmail message id.');
158
-
159
- const message = await fetchMessage(config, accessToken, id, 'full');
160
- const body = findTextPart(message.payload);
161
- return [
162
- formatMessageSummary(message),
163
- '',
164
- body ? `Body:\n${body.slice(0, Number(input.maxChars || 4000))}` : 'Body: (No readable text body found)'
165
- ].join('\n');
166
- }
167
-
168
- function sanitizeHeader(value = '') {
169
- return String(value).replace(/[\r\n]+/g, ' ').trim();
170
- }
171
-
172
- function buildRawEmail(input) {
173
- const to = sanitizeHeader(input.to || input.recipient || '');
174
- if (!to) throw new Error('Missing email recipient.');
175
-
176
- const cc = sanitizeHeader(input.cc || '');
177
- const bcc = sanitizeHeader(input.bcc || '');
178
- const subject = sanitizeHeader(input.subject || '(No subject)');
179
- const body = String(input.body || input.content || input.text || '');
180
-
181
- const headers = [
182
- `To: ${to}`,
183
- cc ? `Cc: ${cc}` : '',
184
- bcc ? `Bcc: ${bcc}` : '',
185
- `Subject: ${subject}`,
186
- 'MIME-Version: 1.0',
187
- 'Content-Type: text/plain; charset="UTF-8"'
188
- ].filter(Boolean);
189
-
190
- return encodeBase64Url(`${headers.join('\r\n')}\r\n\r\n${body}`);
191
- }
192
-
193
- async function createDraft(config, input, accessToken) {
194
- const raw = buildRawEmail(input);
195
- const response = await axios.post(`${GMAIL_API_BASE}/users/${gmailUserId(config)}/drafts`, {
196
- message: { raw }
197
- }, {
198
- headers: gmailHeaders(accessToken)
199
- });
200
-
201
- const draft = response.data || {};
202
- return `Created Gmail draft${draft.id ? ` ${draft.id}` : ''} for ${sanitizeHeader(input.to || input.recipient)}. Review it in Gmail before sending.`;
203
- }
204
-
205
- function helpText() {
206
- return [
207
- 'Gmail plugin commands:',
208
- '- Search inbox: {"action":"search","query":"in:inbox newer_than:7d","limit":5}',
209
- '- Read message: {"action":"read","id":"MESSAGE_ID"}',
210
- '- Create draft: {"action":"draft","to":"person@example.com","subject":"Hello","body":"Draft body"}',
211
- 'For safety, this plugin creates drafts only. It does not send email automatically.'
212
- ].join('\n');
213
- }
214
-
215
- module.exports = {
216
- name: 'gmail',
217
- description: 'Manage Gmail safely. Target can be JSON: {"action":"search","query":"in:inbox is:unread","limit":10}, {"action":"read","id":"MESSAGE_ID"}, or {"action":"draft","to":"person@example.com","subject":"Subject","body":"Body"}. This plugin creates drafts only and does not send email.',
218
-
219
- async execute(instruction) {
220
- const config = readConfig();
221
- const input = parseInstruction(instruction);
222
-
223
- if (input.action === 'help') return helpText();
224
- if (!hasGmailConfig(config)) {
225
- return 'Gmail API is not configured. Add Gmail OAuth credentials with `mint onboard`. Use scopes for gmail.readonly and gmail.compose.';
226
- }
227
-
228
- const accessToken = await getAccessToken(config);
229
-
230
- switch (input.action) {
231
- case 'search':
232
- return await searchMessages(config, input, accessToken);
233
- case 'read':
234
- return await readMessage(config, input, accessToken);
235
- case 'draft':
236
- return await createDraft(config, input, accessToken);
237
- default:
238
- throw new Error(`Unsupported Gmail action: ${input.action}`);
239
- }
240
- },
241
-
242
- _helpers: {
243
- parseInstruction,
244
- buildRawEmail,
245
- decodeBase64Url,
246
- encodeBase64Url,
247
- findTextPart,
248
- formatMessageSummary,
249
- hasGmailConfig
250
- }
251
- };
@@ -1,252 +0,0 @@
1
- const axios = require('axios');
2
- const { readConfig } = require('../System/config_manager');
3
-
4
- let shell = null;
5
- try {
6
- ({ shell } = require('electron'));
7
- } catch {
8
- shell = null;
9
- }
10
-
11
- const TOKEN_URL = 'https://oauth2.googleapis.com/token';
12
- const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3';
13
-
14
- function hasCalendarApiConfig(config) {
15
- return Boolean(
16
- config.googleCalendarClientId &&
17
- config.googleCalendarClientSecret &&
18
- config.googleCalendarRefreshToken
19
- );
20
- }
21
-
22
- function parseInstruction(instruction) {
23
- const raw = (instruction || '').trim();
24
- if (!raw) return { action: 'open' };
25
-
26
- try {
27
- const parsed = JSON.parse(raw);
28
- if (parsed && typeof parsed === 'object') {
29
- return {
30
- action: (parsed.action || 'create').toLowerCase(),
31
- ...parsed
32
- };
33
- }
34
- } catch {
35
- // Plain text remains supported for backward compatibility.
36
- }
37
-
38
- const lower = raw.toLowerCase();
39
- if (['open', 'view', 'calendar'].includes(lower)) return { action: 'open' };
40
- if (['today', 'list today'].includes(lower)) return { action: 'list', range: 'today' };
41
- if (lower.startsWith('list') || lower.startsWith('upcoming')) return { action: 'list', range: 'upcoming' };
42
-
43
- return { action: 'create', summary: raw };
44
- }
45
-
46
- async function getAccessToken(config) {
47
- const params = new URLSearchParams({
48
- client_id: config.googleCalendarClientId,
49
- client_secret: config.googleCalendarClientSecret,
50
- refresh_token: config.googleCalendarRefreshToken,
51
- grant_type: 'refresh_token'
52
- });
53
-
54
- const response = await axios.post(TOKEN_URL, params.toString(), {
55
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
56
- });
57
-
58
- return response.data.access_token;
59
- }
60
-
61
- function getCalendarId(config) {
62
- return config.googleCalendarId || 'primary';
63
- }
64
-
65
- function getLocalDayBounds(date = new Date()) {
66
- const start = new Date(date);
67
- start.setHours(0, 0, 0, 0);
68
-
69
- const end = new Date(start);
70
- end.setDate(end.getDate() + 1);
71
-
72
- return { start, end };
73
- }
74
-
75
- function addDays(date, days) {
76
- const next = new Date(date);
77
- next.setDate(next.getDate() + days);
78
- return next;
79
- }
80
-
81
- function addDaysToIsoDate(dateString, days) {
82
- const [year, month, day] = dateString.split('-').map(Number);
83
- const date = new Date(Date.UTC(year, month - 1, day));
84
- date.setUTCDate(date.getUTCDate() + days);
85
- return date.toISOString().slice(0, 10);
86
- }
87
-
88
- function formatEventTime(event) {
89
- const start = event.start || {};
90
- const end = event.end || {};
91
- const startValue = start.dateTime || start.date;
92
- const endValue = end.dateTime || end.date;
93
-
94
- if (!startValue) return '';
95
- if (start.date) return startValue;
96
-
97
- const startText = new Date(startValue).toLocaleString('th-TH', {
98
- dateStyle: 'medium',
99
- timeStyle: 'short'
100
- });
101
- if (!endValue) return startText;
102
-
103
- const endText = new Date(endValue).toLocaleTimeString('th-TH', {
104
- hour: '2-digit',
105
- minute: '2-digit'
106
- });
107
- return `${startText} - ${endText}`;
108
- }
109
-
110
- function buildEventPayload(input) {
111
- const summary = (input.summary || input.title || input.name || '').trim();
112
- if (!summary) {
113
- throw new Error('Missing event summary/title.');
114
- }
115
-
116
- const payload = {
117
- summary,
118
- description: input.description || undefined,
119
- location: input.location || undefined
120
- };
121
-
122
- if (input.start || input.startDateTime || input.end || input.endDateTime) {
123
- const start = input.start || input.startDateTime;
124
- const end = input.end || input.endDateTime;
125
- if (!start) throw new Error('Missing event start time.');
126
-
127
- payload.start = { dateTime: new Date(start).toISOString() };
128
- payload.end = { dateTime: end ? new Date(end).toISOString() : new Date(new Date(start).getTime() + 60 * 60 * 1000).toISOString() };
129
- } else if (input.date) {
130
- payload.start = { date: input.date };
131
- payload.end = { date: input.endDate || input.date };
132
- if (payload.end.date === payload.start.date) {
133
- payload.end.date = addDaysToIsoDate(input.date, 1);
134
- }
135
- } else {
136
- const now = new Date();
137
- const start = addDays(now, 1);
138
- start.setHours(9, 0, 0, 0);
139
- payload.start = { dateTime: start.toISOString() };
140
- payload.end = { dateTime: new Date(start.getTime() + 60 * 60 * 1000).toISOString() };
141
- }
142
-
143
- return payload;
144
- }
145
-
146
- async function listEvents(config, input, accessToken) {
147
- const now = new Date();
148
- let timeMin = now;
149
- let timeMax = addDays(now, Number(input.days || 7));
150
-
151
- if (input.range === 'today') {
152
- const bounds = getLocalDayBounds(now);
153
- timeMin = bounds.start;
154
- timeMax = bounds.end;
155
- }
156
-
157
- if (input.timeMin) timeMin = new Date(input.timeMin);
158
- if (input.timeMax) timeMax = new Date(input.timeMax);
159
-
160
- const calendarId = encodeURIComponent(getCalendarId(config));
161
- const response = await axios.get(`${CALENDAR_API_BASE}/calendars/${calendarId}/events`, {
162
- headers: { Authorization: `Bearer ${accessToken}` },
163
- params: {
164
- singleEvents: true,
165
- orderBy: 'startTime',
166
- maxResults: Number(input.maxResults || 10),
167
- timeMin: timeMin.toISOString(),
168
- timeMax: timeMax.toISOString()
169
- }
170
- });
171
-
172
- const events = response.data.items || [];
173
- if (events.length === 0) {
174
- return input.range === 'today'
175
- ? 'No Google Calendar events found for today. 📅'
176
- : 'No upcoming Google Calendar events found. 📅';
177
- }
178
-
179
- const lines = events.map((event, index) => {
180
- const when = formatEventTime(event);
181
- return `${index + 1}. ${event.summary || '(Untitled)'}${when ? ` — ${when}` : ''}`;
182
- });
183
-
184
- return `Google Calendar events:\n${lines.join('\n')}`;
185
- }
186
-
187
- async function createEvent(config, input, accessToken) {
188
- const payload = buildEventPayload(input);
189
- const calendarId = encodeURIComponent(getCalendarId(config));
190
- const response = await axios.post(`${CALENDAR_API_BASE}/calendars/${calendarId}/events`, payload, {
191
- headers: {
192
- Authorization: `Bearer ${accessToken}`,
193
- 'Content-Type': 'application/json'
194
- }
195
- });
196
-
197
- const event = response.data || {};
198
- return `Created "${event.summary || payload.summary}" in Google Calendar. 📅${event.htmlLink ? `\n${event.htmlLink}` : ''}`;
199
- }
200
-
201
- function openCalendarFallback(input) {
202
- if (!shell || typeof shell.openExternal !== 'function') {
203
- return 'Google Calendar API is not configured, and this environment cannot open a browser.';
204
- }
205
-
206
- if (input.action === 'open') {
207
- shell.openExternal('https://calendar.google.com/');
208
- return 'Opening Google Calendar. 📅';
209
- }
210
-
211
- const title = encodeURIComponent(input.summary || input.title || input.name || 'New event');
212
- const url = `https://calendar.google.com/calendar/r/eventedit?text=${title}`;
213
- shell.openExternal(url);
214
- return `Google Calendar API is not configured, so I opened the event creation page for "${decodeURIComponent(title)}" instead. 📅`;
215
- }
216
-
217
- module.exports = {
218
- name: 'google_calendar',
219
- description: 'Manage Google Calendar. Target can be JSON: {"action":"list","range":"today|upcoming","days":7} or {"action":"create","summary":"Meeting","start":"2026-05-15T10:00:00+07:00","end":"2026-05-15T11:00:00+07:00","description":"","location":""}. Plain text creates a new event title. Use action "open" to open Calendar.',
220
-
221
- async execute(instruction) {
222
- const config = readConfig();
223
- const input = parseInstruction(instruction);
224
-
225
- if (!hasCalendarApiConfig(config)) {
226
- return openCalendarFallback(input);
227
- }
228
-
229
- const accessToken = await getAccessToken(config);
230
-
231
- if (input.action === 'list' || input.action === 'today' || input.action === 'upcoming') {
232
- return await listEvents(config, input, accessToken);
233
- }
234
-
235
- if (input.action === 'open') {
236
- return openCalendarFallback(input);
237
- }
238
-
239
- if (input.action === 'create' || input.action === 'add') {
240
- return await createEvent(config, input, accessToken);
241
- }
242
-
243
- throw new Error(`Unsupported Google Calendar action: ${input.action}`);
244
- },
245
-
246
- _helpers: {
247
- parseInstruction,
248
- buildEventPayload,
249
- hasCalendarApiConfig,
250
- addDaysToIsoDate
251
- }
252
- };