@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
package/src/UI/index.html DELETED
@@ -1,126 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Agent Mint</title>
8
- <link rel="stylesheet" href="styles.css">
9
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&family=Mali:wght@400;500;600&family=Prompt:wght@400;500&family=Sarabun:wght@400;500&display=swap" rel="stylesheet">
10
- </head>
11
-
12
- <body>
13
- <div class="app-container">
14
- <header class="drag-region">
15
- <div class="header-content">
16
- <div class="status-indicator"></div>
17
- <h1>Agent Mint</h1>
18
- </div>
19
- <button class="clear-btn" id="clear-btn" aria-label="Clear chat" title="Clear chat history">
20
- <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none"
21
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
22
- <polyline points="3 6 5 6 21 6"></polyline>
23
- <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
24
- <path d="M10 11v6"></path>
25
- <path d="M14 11v6"></path>
26
- <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"></path>
27
- </svg>
28
- </button>
29
- <button class="settings-btn" id="settings-btn" aria-label="Settings" title="Settings">
30
- <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none"
31
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
32
- <circle cx="12" cy="12" r="3"></circle>
33
- <path
34
- d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z">
35
- </path>
36
- </svg>
37
- </button>
38
- <button class="minimize-btn" id="minimize-btn" aria-label="Minimize" title="Minimize to Tray">
39
- <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none"
40
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
- <line x1="5" y1="12" x2="19" y2="12"></line>
42
- </svg>
43
- </button>
44
- <button class="maximize-btn" id="maximize-btn" aria-label="Maximize">
45
- <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none"
46
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
47
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
48
- </svg>
49
- </button>
50
- <button class="close-btn" id="close-btn" aria-label="Close">
51
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
52
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
53
- <line x1="18" y1="6" x2="6" y2="18"></line>
54
- <line x1="6" y1="6" x2="18" y2="18"></line>
55
- </svg>
56
- </button>
57
- </header>
58
-
59
- <!-- Proactive Suggestion Bar (Smart Suggestion Engine) -->
60
- <div id="proactive-bar" class="proactive-bar" style="display: none;">
61
- <div class="proactive-header">
62
- <span class="proactive-icon">✨</span>
63
- <span class="proactive-message" id="proactive-message"></span>
64
- <button class="proactive-dismiss-btn" id="proactive-dismiss-btn" title="Dismiss">✕</button>
65
- </div>
66
- <div class="proactive-chips" id="proactive-chips">
67
- <!-- Chips injected by JS -->
68
- </div>
69
- </div>
70
-
71
- <main class="chat-container" id="chat-container">
72
- <div class="message ai-message initial">
73
- <div class="message-bubble">
74
- Hello! I'm Mint, your personal AI assistant ✨ <br>
75
- Is there anything I can help you with, Master? You can ask me to "open YouTube", "create a folder", or just chat with me! 💖
76
- </div>
77
- </div>
78
- </main>
79
-
80
- <footer class="input-area">
81
- <div id="image-preview-container" style="display: none; padding: 10px; background: rgba(0,0,0,0.2); border-radius: 8px 8px 0 0; position: relative;">
82
- <img id="image-preview" src="" style="max-height: 80px; max-width: 100%; border-radius: 4px;">
83
- <button id="remove-image-btn" type="button" style="position: absolute; top: 5px; right: 10px; background: red; color: white; border: none; border-radius: 50%; width: 20px; height: 20px; cursor: pointer;">&times;</button>
84
- </div>
85
-
86
- <!-- Smart Context Toggle -->
87
- <div class="smart-context-bar">
88
- <label class="toggle-switch">
89
- <input type="checkbox" id="smart-context-toggle">
90
- <span class="slider round"></span>
91
- </label>
92
- <span class="smart-context-label">🧠 Smart Context <span style="font-size: 10px; opacity: 0.7;">(Auto-Screen)</span></span>
93
- </div>
94
-
95
- <form id="chat-form">
96
- <button type="button" id="vision-btn" aria-label="Screen Vision" title="Screen Vision">
97
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
98
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
99
- <circle cx="12" cy="12" r="3"></circle>
100
- </svg>
101
- </button>
102
- <input type="text" id="chat-input" placeholder="Type or speak a command..." autocomplete="off">
103
- <button type="button" id="mic-btn" aria-label="Microphone">
104
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
105
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
106
- <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"></path>
107
- <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
108
- <line x1="12" y1="19" x2="12" y2="23"></line>
109
- <line x1="8" y1="23" x2="16" y2="23"></line>
110
- </svg>
111
- </button>
112
- <button type="submit" id="send-btn">
113
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
114
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
115
- <line x1="22" y1="2" x2="11" y2="13"></line>
116
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
117
- </svg>
118
- </button>
119
- </form>
120
- </footer>
121
- </div>
122
-
123
- <script src="renderer.js"></script>
124
- </body>
125
-
126
- </html>
package/tech_news.txt DELETED
@@ -1,3 +0,0 @@
1
- หัวข้อข่าว: DeepL ขยายบริการสู่การแปลด้วยเสียง (Voice Translation)
2
-
3
- สรุป: DeepL แพลตฟอร์มแปลภาษาชั้นนำ ได้ประกาศขยายขีดความสามารถเข้าสู่ตลาดการแปลด้วยเสียง (Voice Translation) เพื่อยกระดับการแข่งขันในตลาดสื่อสารด้วย AI และการแปลภาษาแบบเรียลไทม์
@@ -1,3 +0,0 @@
1
- Project X Secret Code: ALPHA-OMEGA-99
2
- The launch date for Project X is December 25th, 2026.
3
- The lead engineer is Dr. Poom.
@@ -1,41 +0,0 @@
1
- /**
2
- * Tests: agent_orchestrator.js
3
- */
4
-
5
- const orchestrator = require('../src/AI_Brain/agent_orchestrator');
6
-
7
- describe('Agent Orchestrator', () => {
8
- beforeEach(() => {
9
- orchestrator.resetAgent();
10
- });
11
-
12
- test('starts with general agent', () => {
13
- const agent = orchestrator.getCurrentAgent();
14
- expect(agent.name).toBe('Mint Default');
15
- });
16
-
17
- test('can switch to coder agent', () => {
18
- orchestrator.setAgent('coder');
19
- const agent = orchestrator.getCurrentAgent();
20
- expect(agent.name).toBe('Mint Coder');
21
- });
22
-
23
- test('can switch to researcher agent', () => {
24
- orchestrator.setAgent('researcher');
25
- const agent = orchestrator.getCurrentAgent();
26
- expect(agent.name).toBe('Mint Researcher');
27
- });
28
-
29
- test('falls back to general for invalid agent', () => {
30
- orchestrator.setAgent('nonexistent');
31
- const agent = orchestrator.getCurrentAgent();
32
- expect(agent.name).toBe('Mint Default');
33
- });
34
-
35
- test('lists available agents', () => {
36
- const agents = orchestrator.listAgents();
37
- expect(agents).toContain('general');
38
- expect(agents).toContain('coder');
39
- expect(agents).toContain('researcher');
40
- });
41
- });
@@ -1,42 +0,0 @@
1
- /**
2
- * Tests: chat_router routing helpers
3
- */
4
-
5
- jest.mock('@google/genai', () => ({
6
- GoogleGenAI: jest.fn()
7
- }));
8
-
9
- jest.mock('../src/CLI/code_agent', () => ({
10
- executeCodeTask: jest.fn(),
11
- _helpers: {
12
- selectSupportedCodeProvider: jest.fn(() => 'gemini')
13
- }
14
- }));
15
-
16
- jest.mock('../src/System/config_manager', () => ({
17
- readConfig: jest.fn(() => ({})),
18
- getAvailableProviders: jest.fn(() => ['gemini'])
19
- }));
20
-
21
- describe('chat_router helpers', () => {
22
- test('recognizes direct folder open request as chat task', () => {
23
- const { _helpers } = require('../src/CLI/chat_router');
24
- expect(_helpers.isDirectFilesystemActionRequest('เปิดโฟลเดอร์ xidaidai ให้หน่อย')).toBe(true);
25
- });
26
-
27
- test('does not classify direct folder open request as code intent', () => {
28
- const { _helpers } = require('../src/CLI/chat_router');
29
- const route = _helpers.detectCodeIntentHeuristic('open folder xidaidai', process.cwd());
30
- expect(route).toBe(false);
31
- });
32
-
33
- test('treats small file-related request as normal chat', () => {
34
- const { _helpers } = require('../src/CLI/chat_router');
35
- expect(_helpers.isLargeCodeTaskRequest('ดูไฟล์ package.json ให้หน่อย', process.cwd())).toBe(false);
36
- });
37
-
38
- test('treats substantial project fix request as code task', () => {
39
- const { _helpers } = require('../src/CLI/chat_router');
40
- expect(_helpers.isLargeCodeTaskRequest('fix the failing tests in this project and verify the result', process.cwd())).toBe(true);
41
- });
42
- });
@@ -1,69 +0,0 @@
1
- /**
2
- * Tests: code_agent helpers
3
- */
4
-
5
- jest.mock('@google/genai', () => ({
6
- GoogleGenAI: jest.fn()
7
- }));
8
-
9
- jest.mock('axios', () => ({}));
10
-
11
- jest.mock('../src/System/config_manager', () => ({
12
- readConfig: jest.fn(() => ({})),
13
- getAvailableProviders: jest.fn(() => ['ollama', 'gemini'])
14
- }));
15
-
16
- jest.mock('../src/CLI/code_session_memory', () => ({
17
- readWorkspaceSession: jest.fn(() => ({
18
- summary: '',
19
- lastTask: '',
20
- lastVerification: '',
21
- updatedAt: null
22
- })),
23
- writeWorkspaceSession: jest.fn()
24
- }));
25
-
26
- const fs = require('fs');
27
- const path = require('path');
28
- const os = require('os');
29
-
30
- describe('code_agent helpers', () => {
31
- test('extractJson recovers JSON embedded in surrounding text', () => {
32
- const { _helpers } = require('../src/CLI/code_agent');
33
- const parsed = _helpers.extractJson('note\n{"action":"finish","input":{"summary":"ok"}}\nthanks');
34
- expect(parsed.action).toBe('finish');
35
- expect(parsed.input.summary).toBe('ok');
36
- });
37
-
38
- test('selectSupportedCodeProvider falls back away from unsupported code providers', () => {
39
- const { _helpers } = require('../src/CLI/code_agent');
40
- const selected = _helpers.selectSupportedCodeProvider(
41
- { aiProvider: 'ollama' },
42
- ['ollama', 'openai', 'gemini']
43
- );
44
- expect(selected).toBe('openai');
45
- });
46
-
47
- test('selectSupportedCodeProvider keeps configured supported provider when available', () => {
48
- const { _helpers } = require('../src/CLI/code_agent');
49
- const selected = _helpers.selectSupportedCodeProvider(
50
- { aiProvider: 'anthropic' },
51
- ['anthropic', 'gemini']
52
- );
53
- expect(selected).toBe('anthropic');
54
- });
55
-
56
- test('findPaths can locate directories by partial name', async () => {
57
- const { _helpers } = require('../src/CLI/code_agent');
58
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-code-agent-'));
59
- const targetDir = path.join(tempDir, 'projects', 'xidaidai');
60
- fs.mkdirSync(targetDir, { recursive: true });
61
-
62
- try {
63
- const result = await _helpers.findPaths(tempDir, 'xidaidai', 'dir');
64
- expect(result).toContain('[dir] projects/xidaidai');
65
- } finally {
66
- fs.rmSync(tempDir, { recursive: true, force: true });
67
- }
68
- });
69
- });
@@ -1,141 +0,0 @@
1
- /**
2
- * Tests: config_manager.js
3
- * Tests readConfig, writeConfig, and getAvailableProviders.
4
- *
5
- * Isolation strategy: spy on os.homedir() BEFORE requiring config_manager
6
- * so CONFIG_DIR and CONFIG_PATH point to a temp directory, never touching
7
- * the real ~/.config/mint/mint-config.json.
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const os = require('os');
13
-
14
- let tempDir;
15
-
16
- beforeEach(() => {
17
- // 1. Reset module registry so config_manager re-initialises fresh each test
18
- jest.resetModules();
19
-
20
- // 2. Create an isolated temp dir for this test
21
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-cfg-test-'));
22
-
23
- // 3. Mock os.homedir() BEFORE requiring config_manager.
24
- // config_manager computes CONFIG_DIR = os.homedir() + '/.config/mint'
25
- // at load time, so the spy must be active when the module first loads.
26
- jest.spyOn(os, 'homedir').mockReturnValue(tempDir);
27
- });
28
-
29
- afterEach(() => {
30
- jest.restoreAllMocks();
31
- jest.resetModules();
32
- fs.rmSync(tempDir, { recursive: true, force: true });
33
- });
34
-
35
- // Helper — always gets a fresh, isolated instance of config_manager
36
- function getModule() {
37
- return require('../src/System/config_manager');
38
- }
39
-
40
- // ── readConfig ─────────────────────────────────────────────────────────────
41
-
42
- describe('config_manager — readConfig', () => {
43
- test('returns DEFAULT_CONFIG when no config file exists', () => {
44
- const { readConfig } = getModule();
45
- const config = readConfig();
46
- expect(config).toHaveProperty('geminiModel', 'gemini-2.5-flash');
47
- expect(config).toHaveProperty('aiProvider', 'gemini');
48
- expect(config).toHaveProperty('language', 'th-TH');
49
- });
50
-
51
- test('merges saved values with defaults (saved value wins)', () => {
52
- const { readConfig, writeConfig } = getModule();
53
- writeConfig({ geminiModel: 'gemini-2.0-pro' });
54
- const config = readConfig();
55
- expect(config.geminiModel).toBe('gemini-2.0-pro');
56
- // Default fields still present
57
- expect(config.aiProvider).toBe('gemini');
58
- expect(config.language).toBe('th-TH');
59
- });
60
-
61
- test('missing keys in saved file are filled by defaults', () => {
62
- const { readConfig, writeConfig } = getModule();
63
- // Write a partial config (no 'ollamaModel' key)
64
- writeConfig({ geminiModel: 'my-model' });
65
- const config = readConfig();
66
- expect(config.ollamaModel).toBe('llama3:latest'); // default
67
- });
68
- });
69
-
70
- // ── writeConfig ────────────────────────────────────────────────────────────
71
-
72
- describe('config_manager — writeConfig', () => {
73
- test('returns { success: true } on valid write', () => {
74
- const { writeConfig } = getModule();
75
- const result = writeConfig({ testKey: 'testValue' });
76
- expect(result).toEqual({ success: true });
77
- });
78
-
79
- test('written JSON is readable back correctly', () => {
80
- const { readConfig, writeConfig } = getModule();
81
- writeConfig({ geminiModel: 'custom-model-test' });
82
- const config = readConfig();
83
- expect(config.geminiModel).toBe('custom-model-test');
84
- });
85
-
86
- test('config file is actually created on disk', () => {
87
- const { writeConfig, CONFIG_PATH } = getModule();
88
- writeConfig({ geminiModel: 'test' });
89
- expect(fs.existsSync(CONFIG_PATH)).toBe(true);
90
- });
91
- });
92
-
93
- // ── getAvailableProviders ──────────────────────────────────────────────────
94
-
95
- describe('config_manager — getAvailableProviders', () => {
96
- test('always includes ollama (local, no key needed)', () => {
97
- const { getAvailableProviders } = getModule();
98
- expect(getAvailableProviders({})).toContain('ollama');
99
- });
100
-
101
- test('includes gemini when apiKey is set', () => {
102
- const { getAvailableProviders } = getModule();
103
- expect(getAvailableProviders({ apiKey: 'test-key' })).toContain('gemini');
104
- });
105
-
106
- test('includes anthropic when anthropicApiKey is set', () => {
107
- const { getAvailableProviders } = getModule();
108
- expect(getAvailableProviders({ anthropicApiKey: 'ant-key' })).toContain('anthropic');
109
- });
110
-
111
- test('includes openai when openaiApiKey is set', () => {
112
- const { getAvailableProviders } = getModule();
113
- expect(getAvailableProviders({ openaiApiKey: 'oai-key' })).toContain('openai');
114
- });
115
-
116
- test('includes huggingface when hfApiKey is set', () => {
117
- const { getAvailableProviders } = getModule();
118
- expect(getAvailableProviders({ hfApiKey: 'hf-key' })).toContain('huggingface');
119
- });
120
-
121
- test('includes local_openai when localApiBaseUrl is set', () => {
122
- const { getAvailableProviders } = getModule();
123
- expect(
124
- getAvailableProviders({ localApiBaseUrl: 'http://localhost:1234/v1' })
125
- ).toContain('local_openai');
126
- });
127
-
128
- test('does NOT include gemini when no key', () => {
129
- const { getAvailableProviders } = getModule();
130
- const savedEnv = process.env.GEMINI_API_KEY;
131
- delete process.env.GEMINI_API_KEY;
132
- const providers = getAvailableProviders({ apiKey: '' });
133
- expect(providers).not.toContain('gemini');
134
- if (savedEnv) process.env.GEMINI_API_KEY = savedEnv;
135
- });
136
-
137
- test('returns array type', () => {
138
- const { getAvailableProviders } = getModule();
139
- expect(Array.isArray(getAvailableProviders({}))).toBe(true);
140
- });
141
- });
@@ -1,46 +0,0 @@
1
- /**
2
- * Tests: docker.js plugin
3
- */
4
-
5
- jest.mock('child_process', () => ({
6
- execFile: jest.fn()
7
- }));
8
-
9
- let docker;
10
- let execFile;
11
-
12
- beforeEach(() => {
13
- jest.resetModules();
14
- ({ execFile } = require('child_process'));
15
- execFile.mockReset();
16
- docker = require('../src/Plugins/docker');
17
- });
18
-
19
- describe('Docker Plugin', () => {
20
- test('lists running containers', async () => {
21
- execFile.mockImplementation((command, args, callback) => {
22
- callback(null, 'web (Up 2 hours)\n', '');
23
- });
24
-
25
- const result = await docker.execute('list');
26
- expect(execFile).toHaveBeenCalledWith('docker', ['ps', '--format', '{{.Names}} ({{.Status}})'], expect.any(Function));
27
- expect(result).toContain('Running Containers');
28
- expect(result).toContain('web (Up 2 hours)');
29
- });
30
-
31
- test('starts a named container without shell interpolation', async () => {
32
- execFile.mockImplementation((command, args, callback) => {
33
- callback(null, '', '');
34
- });
35
-
36
- const result = await docker.execute('start my-app');
37
- expect(execFile).toHaveBeenCalledWith('docker', ['start', 'my-app'], expect.any(Function));
38
- expect(result).toContain('Successfully executed "docker start"');
39
- });
40
-
41
- test('returns helpful message when command is invalid', async () => {
42
- const result = await docker.execute('remove my-app');
43
- expect(result).toContain('Invalid docker command');
44
- expect(execFile).not.toHaveBeenCalled();
45
- });
46
- });
@@ -1,57 +0,0 @@
1
- /**
2
- * Tests: file_operations helpers
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
-
9
- describe('file_operations findPath', () => {
10
- let tempDir;
11
- let originalCwd;
12
-
13
- beforeEach(() => {
14
- jest.resetModules();
15
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-file-ops-'));
16
- originalCwd = process.cwd();
17
- process.chdir(tempDir);
18
- });
19
-
20
- afterEach(() => {
21
- process.chdir(originalCwd);
22
- fs.rmSync(tempDir, { recursive: true, force: true });
23
- });
24
-
25
- test('finds directory matches by name', () => {
26
- const targetDir = path.join(tempDir, 'nested', 'xidaidai');
27
- fs.mkdirSync(targetDir, { recursive: true });
28
-
29
- const { findPath } = require('../src/Automation_Layer/file_operations');
30
- const result = findPath('xidaidai', { type: 'dir', maxResults: 10, roots: [tempDir] });
31
-
32
- expect(result.success).toBe(true);
33
- expect(result.matches.some(match => match.path === targetDir && match.type === 'dir')).toBe(true);
34
- });
35
-
36
- test('returns not found message when no path matches', () => {
37
- const { findPath } = require('../src/Automation_Layer/file_operations');
38
- const result = findPath('does-not-exist', { type: 'dir', maxResults: 10, roots: [tempDir] });
39
-
40
- expect(result.success).toBe(false);
41
- expect(result.message).toContain('ไม่พบ');
42
- });
43
-
44
- test('prefers exact directory name matches over nested partial matches', () => {
45
- const exactDir = path.join(tempDir, 'xidaidai');
46
- const nestedPartial = path.join(tempDir, 'xidaidai collection', 'xidaidai gif');
47
- fs.mkdirSync(exactDir, { recursive: true });
48
- fs.mkdirSync(nestedPartial, { recursive: true });
49
-
50
- const { findPath } = require('../src/Automation_Layer/file_operations');
51
- const result = findPath('xidaidai', { type: 'dir', maxResults: 10, roots: [tempDir] });
52
-
53
- expect(result.success).toBe(true);
54
- expect(result.matches.length).toBe(1);
55
- expect(result.matches[0].path).toBe(exactDir);
56
- });
57
- });