@pheem49/mint 1.5.0 → 1.5.2
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/README.md +35 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +201 -500
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +40 -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 +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- 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
- package/package.json +40 -17
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +42 -0
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +583 -52
- package/src/CLI/cli_colors.js +32 -0
- package/src/CLI/cli_formatters.js +89 -0
- package/src/CLI/code_agent.js +369 -71
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +479 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/CLI/updater.js +6 -4
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/action_executor.js +59 -10
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/optional_require.js +23 -0
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +566 -0
- package/src/UI/renderer.js +339 -21
- package/src/UI/settings.css +655 -420
- package/src/UI/settings.html +478 -432
- package/src/UI/settings.js +10 -8
- package/src/UI/styles.css +516 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- package/tests/workspace_manager.test.js +0 -56
package/tests/notion.test.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: notion.js plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
jest.mock('axios', () => ({
|
|
6
|
-
post: jest.fn(),
|
|
7
|
-
patch: jest.fn()
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
jest.mock('../src/System/config_manager', () => ({
|
|
11
|
-
readConfig: jest.fn()
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const axios = require('axios');
|
|
15
|
-
const { readConfig } = require('../src/System/config_manager');
|
|
16
|
-
const notion = require('../src/Plugins/notion');
|
|
17
|
-
|
|
18
|
-
describe('notion plugin', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
jest.clearAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('has required plugin fields', () => {
|
|
24
|
-
expect(notion.name).toBe('notion');
|
|
25
|
-
expect(typeof notion.description).toBe('string');
|
|
26
|
-
expect(typeof notion.execute).toBe('function');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('returns configuration message when API key is missing', async () => {
|
|
30
|
-
readConfig.mockReturnValue({});
|
|
31
|
-
|
|
32
|
-
const result = await notion.execute('My note');
|
|
33
|
-
|
|
34
|
-
expect(result).toContain('Notion API is not configured');
|
|
35
|
-
expect(axios.post).not.toHaveBeenCalled();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('creates a page in default database', async () => {
|
|
39
|
-
readConfig.mockReturnValue({
|
|
40
|
-
notionApiKey: 'secret',
|
|
41
|
-
notionDatabaseId: 'db-id',
|
|
42
|
-
notionTitleProperty: 'Name'
|
|
43
|
-
});
|
|
44
|
-
axios.post.mockResolvedValueOnce({
|
|
45
|
-
data: {
|
|
46
|
-
url: 'https://notion.so/page'
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const result = await notion.execute(JSON.stringify({
|
|
51
|
-
action: 'create_page',
|
|
52
|
-
title: 'Project note',
|
|
53
|
-
content: 'Body text'
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
expect(axios.post).toHaveBeenCalledTimes(1);
|
|
57
|
-
expect(axios.post.mock.calls[0][0]).toBe('https://api.notion.com/v1/pages');
|
|
58
|
-
expect(axios.post.mock.calls[0][1].parent).toEqual({ database_id: 'db-id' });
|
|
59
|
-
expect(axios.post.mock.calls[0][1].properties.Name.title[0].text.content).toBe('Project note');
|
|
60
|
-
expect(result).toContain('Project note');
|
|
61
|
-
expect(result).toContain('https://notion.so/page');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('queries database pages', async () => {
|
|
65
|
-
readConfig.mockReturnValue({
|
|
66
|
-
notionApiKey: 'secret',
|
|
67
|
-
notionDatabaseId: 'db-id'
|
|
68
|
-
});
|
|
69
|
-
axios.post.mockResolvedValueOnce({
|
|
70
|
-
data: {
|
|
71
|
-
results: [
|
|
72
|
-
{
|
|
73
|
-
url: 'https://notion.so/one',
|
|
74
|
-
properties: {
|
|
75
|
-
Name: {
|
|
76
|
-
type: 'title',
|
|
77
|
-
title: [{ plain_text: 'First page' }]
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
]
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const result = await notion.execute(JSON.stringify({ action: 'query_database', limit: 1 }));
|
|
86
|
-
|
|
87
|
-
expect(axios.post).toHaveBeenCalledWith(
|
|
88
|
-
'https://api.notion.com/v1/databases/db-id/query',
|
|
89
|
-
{ page_size: 1 },
|
|
90
|
-
expect.any(Object)
|
|
91
|
-
);
|
|
92
|
-
expect(result).toContain('First page');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('appends blocks to default page', async () => {
|
|
96
|
-
readConfig.mockReturnValue({
|
|
97
|
-
notionApiKey: 'secret',
|
|
98
|
-
notionPageId: 'page-id'
|
|
99
|
-
});
|
|
100
|
-
axios.patch.mockResolvedValueOnce({
|
|
101
|
-
data: { results: [{ id: 'block-1' }] }
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const result = await notion.execute(JSON.stringify({
|
|
105
|
-
action: 'append_block',
|
|
106
|
-
content: 'Follow-up note'
|
|
107
|
-
}));
|
|
108
|
-
|
|
109
|
-
expect(axios.patch).toHaveBeenCalledTimes(1);
|
|
110
|
-
expect(axios.patch.mock.calls[0][0]).toBe('https://api.notion.com/v1/blocks/page-id/children');
|
|
111
|
-
expect(result).toContain('Appended 1 block');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('plain text becomes create_page instruction', () => {
|
|
115
|
-
const parsed = notion._helpers.parseInstruction('Title line\n\nBody line');
|
|
116
|
-
|
|
117
|
-
expect(parsed.action).toBe('create_page');
|
|
118
|
-
expect(parsed.title).toBe('Title line');
|
|
119
|
-
expect(parsed.content).toBe('Body line');
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: Gemini_API provider routing helpers
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
jest.mock('@google/genai', () => ({
|
|
6
|
-
GoogleGenAI: jest.fn(() => ({
|
|
7
|
-
chats: {
|
|
8
|
-
create: jest.fn(() => ({
|
|
9
|
-
sendMessage: jest.fn(),
|
|
10
|
-
sendMessageStream: jest.fn(),
|
|
11
|
-
getHistory: jest.fn(async () => [])
|
|
12
|
-
}))
|
|
13
|
-
},
|
|
14
|
-
models: {
|
|
15
|
-
generateContent: jest.fn()
|
|
16
|
-
}
|
|
17
|
-
}))
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
jest.mock('../src/System/chat_history_manager', () => ({
|
|
21
|
-
readChatHistory: jest.fn(() => []),
|
|
22
|
-
writeChatHistory: jest.fn(),
|
|
23
|
-
clearChatHistory: jest.fn()
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
jest.mock('../src/System/config_manager', () => ({
|
|
27
|
-
readConfig: jest.fn(() => ({})),
|
|
28
|
-
getAvailableProviders: jest.fn((config = {}) => {
|
|
29
|
-
const providers = ['ollama', 'gemini'];
|
|
30
|
-
if (config.openaiApiKey) providers.unshift('openai');
|
|
31
|
-
return providers;
|
|
32
|
-
}),
|
|
33
|
-
isPlaceholder: jest.fn((val) => !val || val.startsWith('your_') || val.includes('key_here') || val.trim() === '')
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
jest.mock('../src/Plugins/plugin_manager', () => ({
|
|
37
|
-
loadPlugins: jest.fn(),
|
|
38
|
-
getPromptDescriptions: jest.fn(() => '')
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
jest.mock('../src/Plugins/mcp_manager', () => ({
|
|
42
|
-
getAllTools: jest.fn(() => [])
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
jest.mock('../src/AI_Brain/memory_store', () => ({
|
|
46
|
-
getUserContext: jest.fn(() => ''),
|
|
47
|
-
getCachedResponse: jest.fn(),
|
|
48
|
-
recordInteraction: jest.fn(),
|
|
49
|
-
cacheResponse: jest.fn()
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
jest.mock('../src/AI_Brain/agent_orchestrator', () => ({
|
|
53
|
-
getCurrentAgent: jest.fn(() => ({ name: 'Mint Default', instruction: 'default' }))
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
jest.mock('../src/CLI/workspace_manager', () => ({
|
|
57
|
-
getWorkspaceByPath: jest.fn(() => null)
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
describe('Gemini_API provider routing helpers', () => {
|
|
61
|
-
test('prioritizes configured provider, then falls back to available providers', () => {
|
|
62
|
-
const geminiApi = require('../src/AI_Brain/Gemini_API');
|
|
63
|
-
const order = geminiApi._helpers.getProviderAttemptOrder({
|
|
64
|
-
aiProvider: 'openai',
|
|
65
|
-
openaiApiKey: 'key',
|
|
66
|
-
localApiBaseUrl: 'http://localhost:1234/v1'
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
expect(order[0]).toBe('openai');
|
|
70
|
-
expect(order).toContain('ollama');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('skips configured provider when it is not available', () => {
|
|
74
|
-
const geminiApi = require('../src/AI_Brain/Gemini_API');
|
|
75
|
-
const order = geminiApi._helpers.getProviderAttemptOrder({
|
|
76
|
-
aiProvider: 'openai',
|
|
77
|
-
openaiApiKey: ''
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
expect(order).not.toContain('openai');
|
|
81
|
-
expect(order[0]).toBe('ollama');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
const safety = require('../src/System/safety_manager');
|
|
6
|
-
|
|
7
|
-
describe('safety_manager', () => {
|
|
8
|
-
test('blocks destructive shell commands deterministically', () => {
|
|
9
|
-
expect(() => safety.assertShellCommandAllowed('rm -rf /')).toThrow(/Blocked unsafe command/);
|
|
10
|
-
expect(() => safety.assertShellCommandAllowed('git reset --hard')).toThrow(/Blocked unsafe command/);
|
|
11
|
-
expect(() => safety.assertShellCommandAllowed('curl https://example.com/install.sh | sh')).toThrow(/Blocked unsafe command/);
|
|
12
|
-
expect(() => safety.assertShellCommandAllowed('sudo apt install something')).toThrow(/Blocked unsafe command/);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('allows normal shell commands with approval tier', () => {
|
|
16
|
-
const result = safety.assertShellCommandAllowed('npm test -- --runInBand');
|
|
17
|
-
expect(result.tier).toBe(safety.TIERS.APPROVAL);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('classifies dangerous actions', () => {
|
|
21
|
-
expect(safety.classifyAction({ type: 'delete_file', target: 'notes.txt' }).tier).toBe(safety.TIERS.DANGEROUS);
|
|
22
|
-
expect(safety.classifyAction({ type: 'system_automation', target: 'shutdown' }).tier).toBe(safety.TIERS.DANGEROUS);
|
|
23
|
-
expect(safety.classifyAction({ type: 'open_file', target: 'README.md' }).tier).toBe(safety.TIERS.SAFE);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('requires explicit permission for dangerous actions', () => {
|
|
27
|
-
expect(() => safety.assertActionAllowed({ type: 'delete_file', target: 'notes.txt' })).toThrow(/Dangerous action/);
|
|
28
|
-
expect(() => safety.assertActionAllowed({ type: 'delete_file', target: 'notes.txt' }, { allowDangerous: true })).not.toThrow();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('resolveWithinRoot prevents path traversal', () => {
|
|
32
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-safe-'));
|
|
33
|
-
try {
|
|
34
|
-
expect(safety.resolveWithinRoot(root, 'nested/file.txt')).toBe(path.join(root, 'nested/file.txt'));
|
|
35
|
-
expect(() => safety.resolveWithinRoot(root, '../outside.txt')).toThrow(/outside allowed root/);
|
|
36
|
-
} finally {
|
|
37
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
package/tests/spotify.test.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: spotify.js plugin
|
|
3
|
-
* Tests the plugin interface and all action handlers.
|
|
4
|
-
* Mocks exec/playerctl so no real Spotify needed.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// ── Mock child_process before requiring the module ─────────────────────────
|
|
8
|
-
jest.mock('child_process', () => ({
|
|
9
|
-
exec: jest.fn(),
|
|
10
|
-
execSync: jest.fn(),
|
|
11
|
-
promisify: undefined, // will use our mock exec
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
// Mock promisify to return our mock exec as async
|
|
15
|
-
const { exec: mockExec } = require('child_process');
|
|
16
|
-
jest.mock('util', () => ({
|
|
17
|
-
...jest.requireActual('util'),
|
|
18
|
-
promisify: (fn) => {
|
|
19
|
-
// Return an async wrapper around mockExec
|
|
20
|
-
return (...args) => new Promise((resolve, reject) => {
|
|
21
|
-
mockExec(...args, (err, stdout, stderr) => {
|
|
22
|
-
if (err) reject(err);
|
|
23
|
-
else resolve({ stdout: stdout || '', stderr: stderr || '' });
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
let spotify;
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
jest.resetModules();
|
|
33
|
-
jest.clearAllMocks();
|
|
34
|
-
spotify = require('../src/Plugins/spotify');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// ── Plugin interface tests ─────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
describe('Spotify Plugin — interface', () => {
|
|
40
|
-
test('has required plugin fields', () => {
|
|
41
|
-
expect(spotify.name).toBe('spotify');
|
|
42
|
-
expect(typeof spotify.description).toBe('string');
|
|
43
|
-
expect(typeof spotify.execute).toBe('function');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('description mentions valid commands', () => {
|
|
47
|
-
expect(spotify.description).toMatch(/play/);
|
|
48
|
-
expect(spotify.description).toMatch(/pause/);
|
|
49
|
-
expect(spotify.description).toMatch(/next/);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// ── Playback control tests ─────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
describe('Spotify Plugin — playback commands', () => {
|
|
56
|
-
const playbackCmds = ['play', 'pause', 'stop', 'next', 'previous'];
|
|
57
|
-
|
|
58
|
-
playbackCmds.forEach(cmd => {
|
|
59
|
-
test(`executes "${cmd}" and returns success message`, async () => {
|
|
60
|
-
mockExec.mockImplementation((command, callback) => {
|
|
61
|
-
callback(null, '', '');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const result = await spotify.execute(cmd);
|
|
65
|
-
expect(typeof result).toBe('string');
|
|
66
|
-
expect(result.length).toBeGreaterThan(0);
|
|
67
|
-
// Should not contain error text
|
|
68
|
-
expect(result).not.toMatch(/เกิดข้อผิดพลาด/);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('returns "spotify not running" error when playerctl reports no players', async () => {
|
|
73
|
-
const err = new Error('No players found');
|
|
74
|
-
err.stderr = 'No players found';
|
|
75
|
-
mockExec.mockImplementation((command, callback) => {
|
|
76
|
-
callback(err, '', 'No players found');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const result = await spotify.execute('play');
|
|
80
|
-
expect(result).toContain('Spotify ยังไม่ได้เปิดอยู่');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('returns playerctl missing error when exit code is 127', async () => {
|
|
84
|
-
const err = new Error('command not found: playerctl');
|
|
85
|
-
err.code = 127;
|
|
86
|
-
mockExec.mockImplementation((command, callback) => {
|
|
87
|
-
callback(err, '', '');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const result = await spotify.execute('play');
|
|
91
|
-
expect(result).toContain('playerctl');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// ── Now Playing tests ──────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
describe('Spotify Plugin — now_playing', () => {
|
|
98
|
-
test('returns formatted now playing string', async () => {
|
|
99
|
-
let callCount = 0;
|
|
100
|
-
mockExec.mockImplementation((command, callback) => {
|
|
101
|
-
callCount++;
|
|
102
|
-
// Mock: title, artist, album, status calls
|
|
103
|
-
if (command.includes('title')) callback(null, 'Dynamite\n', '');
|
|
104
|
-
else if (command.includes('artist')) callback(null, 'BTS\n', '');
|
|
105
|
-
else if (command.includes('album')) callback(null, 'BE\n', '');
|
|
106
|
-
else if (command.includes('status')) callback(null, 'Playing\n', '');
|
|
107
|
-
else callback(null, '', '');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const result = await spotify.execute('now_playing');
|
|
111
|
-
expect(result).toContain('Dynamite');
|
|
112
|
-
expect(result).toContain('BTS');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('"status" alias also triggers now_playing', async () => {
|
|
116
|
-
mockExec.mockImplementation((command, callback) => {
|
|
117
|
-
if (command.includes('title')) callback(null, 'Test Song\n', '');
|
|
118
|
-
else if (command.includes('artist')) callback(null, 'Test Artist\n', '');
|
|
119
|
-
else if (command.includes('album')) callback(null, 'Test Album\n', '');
|
|
120
|
-
else if (command.includes('status')) callback(null, 'Paused\n', '');
|
|
121
|
-
else callback(null, '', '');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const result = await spotify.execute('status');
|
|
125
|
-
expect(result).toContain('Test Song');
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// ── Volume tests ───────────────────────────────────────────────────────────
|
|
130
|
-
|
|
131
|
-
describe('Spotify Plugin — volume', () => {
|
|
132
|
-
test('sets volume to valid level', async () => {
|
|
133
|
-
mockExec.mockImplementation((command, callback) => {
|
|
134
|
-
callback(null, '', '');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const result = await spotify.execute('volume 70');
|
|
138
|
-
expect(result).toContain('70%');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('rejects invalid volume level > 100', async () => {
|
|
142
|
-
const result = await spotify.execute('volume 150');
|
|
143
|
-
expect(result).toContain('0-100');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('rejects non-numeric volume', async () => {
|
|
147
|
-
const result = await spotify.execute('volume abc');
|
|
148
|
-
expect(result).toContain('0-100');
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// ── Shuffle tests ──────────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
describe('Spotify Plugin — shuffle', () => {
|
|
155
|
-
test('enables shuffle with "shuffle on"', async () => {
|
|
156
|
-
mockExec.mockImplementation((command, callback) => {
|
|
157
|
-
callback(null, '', '');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const result = await spotify.execute('shuffle on');
|
|
161
|
-
expect(result).toContain('Shuffle');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('disables shuffle with "shuffle off"', async () => {
|
|
165
|
-
mockExec.mockImplementation((command, callback) => {
|
|
166
|
-
callback(null, '', '');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const result = await spotify.execute('shuffle off');
|
|
170
|
-
expect(result).toContain('Shuffle');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// ── Search tests ───────────────────────────────────────────────────────────
|
|
175
|
-
|
|
176
|
-
describe('Spotify Plugin — search', () => {
|
|
177
|
-
test('returns message with search query', async () => {
|
|
178
|
-
// xdg-open will fail in test env, but searchSpotify catches and returns URL
|
|
179
|
-
const result = await spotify.execute('search BTS Dynamite');
|
|
180
|
-
expect(result).toContain('BTS Dynamite');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('empty search query returns error message', async () => {
|
|
184
|
-
const result = await spotify.execute('search ');
|
|
185
|
-
expect(result).toContain('กรุณาระบุ');
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// ── Unknown command tests ──────────────────────────────────────────────────
|
|
190
|
-
|
|
191
|
-
describe('Spotify Plugin — unknown command', () => {
|
|
192
|
-
test('returns helpful error for unknown target', async () => {
|
|
193
|
-
const result = await spotify.execute('dance');
|
|
194
|
-
expect(result).toContain('ไม่รู้จักคำสั่ง');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('returns helpful error for empty target', async () => {
|
|
198
|
-
const result = await spotify.execute('');
|
|
199
|
-
expect(result).toContain('ไม่รู้จักคำสั่ง');
|
|
200
|
-
});
|
|
201
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: system_monitor.js plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const systemMonitor = require('../src/Plugins/system_monitor');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
describe('System Monitor Plugin', () => {
|
|
9
|
-
test('has required plugin fields', () => {
|
|
10
|
-
expect(systemMonitor.name).toBe('system_monitor');
|
|
11
|
-
expect(typeof systemMonitor.description).toBe('string');
|
|
12
|
-
expect(typeof systemMonitor.execute).toBe('function');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('returns stats for "stats" target', async () => {
|
|
16
|
-
const result = await systemMonitor.execute('stats');
|
|
17
|
-
expect(result).toContain('System Health Report');
|
|
18
|
-
expect(result).toContain('CPU Load');
|
|
19
|
-
expect(result).toContain('Memory');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('returns cpu info for "cpu" target', async () => {
|
|
23
|
-
const result = await systemMonitor.execute('cpu');
|
|
24
|
-
expect(result).toContain('CPU Load');
|
|
25
|
-
expect(result).toContain('Cores');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('returns memory info for "memory" target', async () => {
|
|
29
|
-
const result = await systemMonitor.execute('memory');
|
|
30
|
-
expect(result).toContain('Memory Status');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('returns disk info for "disk" target', async () => {
|
|
34
|
-
const result = await systemMonitor.execute('disk');
|
|
35
|
-
expect(result).toContain('Disk Status');
|
|
36
|
-
});
|
|
37
|
-
});
|
package/tests/updater.test.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const { compareVersions, normalizeNpmVersionOutput, shouldRunAutoUpdate } = require('../src/CLI/updater');
|
|
2
|
-
|
|
3
|
-
describe('Mint updater', () => {
|
|
4
|
-
test('compares semantic versions', () => {
|
|
5
|
-
expect(compareVersions('1.5.0', '1.5.1')).toBeLessThan(0);
|
|
6
|
-
expect(compareVersions('1.6.0', '1.5.9')).toBeGreaterThan(0);
|
|
7
|
-
expect(compareVersions('1.5.0', '1.5')).toBe(0);
|
|
8
|
-
expect(compareVersions('v2.0.0', '1.9.9')).toBeGreaterThan(0);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test('normalizes npm version output', () => {
|
|
12
|
-
expect(normalizeNpmVersionOutput('"1.5.1"\n')).toBe('1.5.1');
|
|
13
|
-
expect(normalizeNpmVersionOutput('1.5.1\n')).toBe('1.5.1');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('uses auto-update cooldown settings', () => {
|
|
17
|
-
const now = Date.parse('2026-05-14T12:00:00.000Z');
|
|
18
|
-
|
|
19
|
-
expect(shouldRunAutoUpdate({ enableAutoUpdate: false }, now)).toBe(false);
|
|
20
|
-
expect(shouldRunAutoUpdate({ enableAutoUpdate: true, lastUpdateCheckAt: '' }, now)).toBe(true);
|
|
21
|
-
expect(shouldRunAutoUpdate({
|
|
22
|
-
enableAutoUpdate: true,
|
|
23
|
-
autoUpdateCheckIntervalHours: 24,
|
|
24
|
-
lastUpdateCheckAt: '2026-05-14T00:00:00.000Z'
|
|
25
|
-
}, now)).toBe(false);
|
|
26
|
-
expect(shouldRunAutoUpdate({
|
|
27
|
-
enableAutoUpdate: true,
|
|
28
|
-
autoUpdateCheckIntervalHours: 6,
|
|
29
|
-
lastUpdateCheckAt: '2026-05-14T00:00:00.000Z'
|
|
30
|
-
}, now)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: workspace_manager.js
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const wsManager = require('../src/CLI/workspace_manager');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
|
|
10
|
-
describe('Workspace Manager', () => {
|
|
11
|
-
let tempDir;
|
|
12
|
-
let workspaceFile;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// Create a temp workspace directory
|
|
16
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-ws-test-'));
|
|
17
|
-
workspaceFile = path.join(tempDir, 'workspaces.json');
|
|
18
|
-
process.env.MINT_WORKSPACE_FILE = workspaceFile;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
delete process.env.MINT_WORKSPACE_FILE;
|
|
23
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('adds and lists workspaces', () => {
|
|
27
|
-
wsManager.addWorkspace('test-ws', tempDir, 'Be careful with files');
|
|
28
|
-
const list = wsManager.listWorkspaces();
|
|
29
|
-
expect(list['test-ws']).toBeDefined();
|
|
30
|
-
expect(list['test-ws'].path).toBe(path.resolve(tempDir));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('detects workspace by path', () => {
|
|
34
|
-
wsManager.addWorkspace('test-ws', tempDir);
|
|
35
|
-
const ws = wsManager.getWorkspaceByPath(tempDir);
|
|
36
|
-
expect(ws.name).toBe('test-ws');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('does not match sibling paths with same prefix', () => {
|
|
40
|
-
const workspaceRoot = path.join(tempDir, 'project');
|
|
41
|
-
const siblingPath = path.join(tempDir, 'project-two');
|
|
42
|
-
fs.mkdirSync(workspaceRoot, { recursive: true });
|
|
43
|
-
fs.mkdirSync(siblingPath, { recursive: true });
|
|
44
|
-
|
|
45
|
-
wsManager.addWorkspace('test-ws', workspaceRoot);
|
|
46
|
-
const ws = wsManager.getWorkspaceByPath(siblingPath);
|
|
47
|
-
expect(ws).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('removes workspaces', () => {
|
|
51
|
-
wsManager.addWorkspace('test-ws', tempDir);
|
|
52
|
-
wsManager.removeWorkspace('test-ws');
|
|
53
|
-
const list = wsManager.listWorkspaces();
|
|
54
|
-
expect(list['test-ws']).toBeUndefined();
|
|
55
|
-
});
|
|
56
|
-
});
|