@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.
- package/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- 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
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /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
- /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
- /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
- /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
- /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
- /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
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /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
- /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
- /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
- /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
- /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
- /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
- /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
- /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
- /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
- /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 };
|
package/src/Plugins/dev_tools.js
DELETED
|
@@ -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
|
-
};
|
package/src/Plugins/discord.js
DELETED
|
@@ -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
|
-
};
|
package/src/Plugins/docker.js
DELETED
|
@@ -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
|
-
};
|
package/src/Plugins/gmail.js
DELETED
|
@@ -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
|
-
};
|