@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/gmail.test.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: gmail.js plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
jest.mock('axios', () => ({
|
|
6
|
-
post: jest.fn(),
|
|
7
|
-
get: 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 gmail = require('../src/Plugins/gmail');
|
|
17
|
-
|
|
18
|
-
describe('gmail plugin', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
jest.clearAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('has required plugin fields', () => {
|
|
24
|
-
expect(gmail.name).toBe('gmail');
|
|
25
|
-
expect(typeof gmail.description).toBe('string');
|
|
26
|
-
expect(typeof gmail.execute).toBe('function');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('returns configuration message when OAuth config is missing', async () => {
|
|
30
|
-
readConfig.mockReturnValue({});
|
|
31
|
-
|
|
32
|
-
const result = await gmail.execute('inbox');
|
|
33
|
-
|
|
34
|
-
expect(result).toContain('Gmail API is not configured');
|
|
35
|
-
expect(axios.get).not.toHaveBeenCalled();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('searches Gmail and fetches metadata for results', async () => {
|
|
39
|
-
readConfig.mockReturnValue({
|
|
40
|
-
gmailClientId: 'client-id',
|
|
41
|
-
gmailClientSecret: 'client-secret',
|
|
42
|
-
gmailRefreshToken: 'refresh-token',
|
|
43
|
-
gmailUserId: 'me'
|
|
44
|
-
});
|
|
45
|
-
axios.post.mockResolvedValueOnce({ data: { access_token: 'access-token' } });
|
|
46
|
-
axios.get
|
|
47
|
-
.mockResolvedValueOnce({ data: { messages: [{ id: 'msg-1' }] } })
|
|
48
|
-
.mockResolvedValueOnce({
|
|
49
|
-
data: {
|
|
50
|
-
id: 'msg-1',
|
|
51
|
-
snippet: 'Hello snippet',
|
|
52
|
-
payload: {
|
|
53
|
-
headers: [
|
|
54
|
-
{ name: 'From', value: 'A <a@example.com>' },
|
|
55
|
-
{ name: 'Subject', value: 'Hello' },
|
|
56
|
-
{ name: 'Date', value: 'Fri, 15 May 2026 10:00:00 +0700' }
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const result = await gmail.execute(JSON.stringify({ action: 'search', query: 'is:unread', limit: 1 }));
|
|
63
|
-
|
|
64
|
-
expect(axios.get).toHaveBeenCalledTimes(2);
|
|
65
|
-
expect(axios.get.mock.calls[0][0]).toBe('https://gmail.googleapis.com/gmail/v1/users/me/messages');
|
|
66
|
-
expect(result).toContain('Hello');
|
|
67
|
-
expect(result).toContain('msg-1');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('reads full Gmail message body', async () => {
|
|
71
|
-
readConfig.mockReturnValue({
|
|
72
|
-
gmailClientId: 'client-id',
|
|
73
|
-
gmailClientSecret: 'client-secret',
|
|
74
|
-
gmailRefreshToken: 'refresh-token',
|
|
75
|
-
gmailUserId: 'me'
|
|
76
|
-
});
|
|
77
|
-
axios.post.mockResolvedValueOnce({ data: { access_token: 'access-token' } });
|
|
78
|
-
axios.get.mockResolvedValueOnce({
|
|
79
|
-
data: {
|
|
80
|
-
id: 'msg-1',
|
|
81
|
-
payload: {
|
|
82
|
-
headers: [
|
|
83
|
-
{ name: 'From', value: 'A <a@example.com>' },
|
|
84
|
-
{ name: 'Subject', value: 'Hello' }
|
|
85
|
-
],
|
|
86
|
-
mimeType: 'text/plain',
|
|
87
|
-
body: { data: gmail._helpers.encodeBase64Url('Full body text') }
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const result = await gmail.execute(JSON.stringify({ action: 'read', id: 'msg-1' }));
|
|
93
|
-
|
|
94
|
-
expect(result).toContain('Full body text');
|
|
95
|
-
expect(result).toContain('Hello');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('creates Gmail draft only', async () => {
|
|
99
|
-
readConfig.mockReturnValue({
|
|
100
|
-
gmailClientId: 'client-id',
|
|
101
|
-
gmailClientSecret: 'client-secret',
|
|
102
|
-
gmailRefreshToken: 'refresh-token',
|
|
103
|
-
gmailUserId: 'me'
|
|
104
|
-
});
|
|
105
|
-
axios.post
|
|
106
|
-
.mockResolvedValueOnce({ data: { access_token: 'access-token' } })
|
|
107
|
-
.mockResolvedValueOnce({ data: { id: 'draft-1' } });
|
|
108
|
-
|
|
109
|
-
const result = await gmail.execute(JSON.stringify({
|
|
110
|
-
action: 'draft',
|
|
111
|
-
to: 'person@example.com',
|
|
112
|
-
subject: 'Draft subject',
|
|
113
|
-
body: 'Draft body'
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
expect(axios.post).toHaveBeenCalledTimes(2);
|
|
117
|
-
expect(axios.post.mock.calls[1][0]).toBe('https://gmail.googleapis.com/gmail/v1/users/me/drafts');
|
|
118
|
-
expect(axios.post.mock.calls[1][1].message.raw).toBeTruthy();
|
|
119
|
-
expect(result).toContain('Created Gmail draft');
|
|
120
|
-
expect(result).toContain('Review it in Gmail before sending');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('buildRawEmail removes newline injection from headers', () => {
|
|
124
|
-
const raw = gmail._helpers.buildRawEmail({
|
|
125
|
-
to: 'person@example.com\nBcc: attacker@example.com',
|
|
126
|
-
subject: 'Hello\r\nBad: header',
|
|
127
|
-
body: 'Body'
|
|
128
|
-
});
|
|
129
|
-
const decoded = gmail._helpers.decodeBase64Url(raw);
|
|
130
|
-
|
|
131
|
-
expect(decoded).toContain('To: person@example.com Bcc: attacker@example.com');
|
|
132
|
-
expect(decoded).toContain('Subject: Hello Bad: header');
|
|
133
|
-
expect(decoded).not.toContain('\nBcc: attacker@example.com');
|
|
134
|
-
});
|
|
135
|
-
});
|
package/tests/gmail_auth.test.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: gmail_auth.js OAuth helper
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
jest.mock('axios', () => ({
|
|
6
|
-
post: jest.fn()
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
jest.mock('../src/System/config_manager', () => ({
|
|
10
|
-
readConfig: jest.fn(),
|
|
11
|
-
writeConfig: jest.fn()
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const axios = require('axios');
|
|
15
|
-
const gmailAuth = require('../src/CLI/gmail_auth');
|
|
16
|
-
|
|
17
|
-
describe('gmail_auth helper', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('buildAuthUrl includes offline access and consent prompt', () => {
|
|
23
|
-
const url = gmailAuth.buildAuthUrl({
|
|
24
|
-
clientId: 'client-id',
|
|
25
|
-
redirectUri: 'http://127.0.0.1:3333/oauth2callback',
|
|
26
|
-
state: 'state-1'
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const parsed = new URL(url);
|
|
30
|
-
expect(parsed.origin + parsed.pathname).toBe('https://accounts.google.com/o/oauth2/v2/auth');
|
|
31
|
-
expect(parsed.searchParams.get('client_id')).toBe('client-id');
|
|
32
|
-
expect(parsed.searchParams.get('access_type')).toBe('offline');
|
|
33
|
-
expect(parsed.searchParams.get('prompt')).toBe('consent');
|
|
34
|
-
expect(parsed.searchParams.get('scope')).toContain('gmail.readonly');
|
|
35
|
-
expect(parsed.searchParams.get('scope')).toContain('gmail.compose');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('exchanges authorization code for token', async () => {
|
|
39
|
-
axios.post.mockResolvedValueOnce({
|
|
40
|
-
data: {
|
|
41
|
-
refresh_token: 'refresh-token'
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const token = await gmailAuth.exchangeCodeForToken({
|
|
46
|
-
clientId: 'client-id',
|
|
47
|
-
clientSecret: 'client-secret',
|
|
48
|
-
code: 'code-1',
|
|
49
|
-
redirectUri: 'http://127.0.0.1:3333/oauth2callback'
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(token.refresh_token).toBe('refresh-token');
|
|
53
|
-
expect(axios.post).toHaveBeenCalledWith(
|
|
54
|
-
'https://oauth2.googleapis.com/token',
|
|
55
|
-
expect.stringContaining('grant_type=authorization_code'),
|
|
56
|
-
expect.any(Object)
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('runGmailAuth opens browser, accepts callback, and saves refresh token', async () => {
|
|
61
|
-
axios.post.mockResolvedValueOnce({
|
|
62
|
-
data: {
|
|
63
|
-
refresh_token: 'new-refresh-token'
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const writeConfig = jest.fn(() => ({ success: true }));
|
|
68
|
-
const result = await gmailAuth.runGmailAuth({
|
|
69
|
-
readConfig: () => ({
|
|
70
|
-
gmailClientId: 'client-id',
|
|
71
|
-
gmailClientSecret: 'client-secret',
|
|
72
|
-
gmailUserId: 'me'
|
|
73
|
-
}),
|
|
74
|
-
writeConfig,
|
|
75
|
-
logger: { log: jest.fn() },
|
|
76
|
-
openBrowser: jest.fn(),
|
|
77
|
-
getAuthorizationCode: async ({ authUrl, state, redirectUri }) => {
|
|
78
|
-
expect(authUrl).toContain(encodeURIComponent(redirectUri));
|
|
79
|
-
expect(state).toBeTruthy();
|
|
80
|
-
return 'auth-code';
|
|
81
|
-
},
|
|
82
|
-
timeoutMs: 5000
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(result.success).toBe(true);
|
|
86
|
-
expect(writeConfig).toHaveBeenCalledWith(expect.objectContaining({
|
|
87
|
-
gmailRefreshToken: 'new-refresh-token',
|
|
88
|
-
gmailUserId: 'me',
|
|
89
|
-
pluginGmailEnabled: true
|
|
90
|
-
}));
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('runGmailAuth can print link without opening browser', async () => {
|
|
94
|
-
axios.post.mockResolvedValueOnce({
|
|
95
|
-
data: {
|
|
96
|
-
refresh_token: 'manual-refresh-token'
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const openBrowser = jest.fn();
|
|
101
|
-
const writeConfig = jest.fn(() => ({ success: true }));
|
|
102
|
-
|
|
103
|
-
const result = await gmailAuth.runGmailAuth({
|
|
104
|
-
readConfig: () => ({
|
|
105
|
-
gmailClientId: 'client-id',
|
|
106
|
-
gmailClientSecret: 'client-secret',
|
|
107
|
-
gmailUserId: 'me'
|
|
108
|
-
}),
|
|
109
|
-
writeConfig,
|
|
110
|
-
logger: { log: jest.fn() },
|
|
111
|
-
openBrowser: false,
|
|
112
|
-
getAuthorizationCode: async () => 'manual-code'
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(result.success).toBe(true);
|
|
116
|
-
expect(openBrowser).not.toHaveBeenCalled();
|
|
117
|
-
expect(writeConfig).toHaveBeenCalledWith(expect.objectContaining({
|
|
118
|
-
gmailRefreshToken: 'manual-refresh-token'
|
|
119
|
-
}));
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('runGmailAuth requires saved client credentials', async () => {
|
|
123
|
-
await expect(gmailAuth.runGmailAuth({
|
|
124
|
-
readConfig: () => ({}),
|
|
125
|
-
openBrowser: jest.fn(),
|
|
126
|
-
logger: { log: jest.fn() }
|
|
127
|
-
})).rejects.toThrow('Missing Gmail OAuth Client ID');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: google_calendar.js plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
jest.mock('axios', () => ({
|
|
6
|
-
post: jest.fn(),
|
|
7
|
-
get: jest.fn()
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
jest.mock('electron', () => ({
|
|
11
|
-
shell: {
|
|
12
|
-
openExternal: jest.fn()
|
|
13
|
-
}
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
jest.mock('../src/System/config_manager', () => ({
|
|
17
|
-
readConfig: jest.fn()
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
const axios = require('axios');
|
|
21
|
-
const { shell } = require('electron');
|
|
22
|
-
const { readConfig } = require('../src/System/config_manager');
|
|
23
|
-
|
|
24
|
-
describe('google_calendar plugin', () => {
|
|
25
|
-
let plugin;
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
jest.clearAllMocks();
|
|
29
|
-
plugin = require('../src/Plugins/google_calendar');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('has required plugin fields', () => {
|
|
33
|
-
expect(plugin.name).toBe('google_calendar');
|
|
34
|
-
expect(typeof plugin.description).toBe('string');
|
|
35
|
-
expect(typeof plugin.execute).toBe('function');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('falls back to opening Google Calendar when API config is missing', async () => {
|
|
39
|
-
readConfig.mockReturnValue({});
|
|
40
|
-
|
|
41
|
-
const result = await plugin.execute('open');
|
|
42
|
-
|
|
43
|
-
expect(shell.openExternal).toHaveBeenCalledWith('https://calendar.google.com/');
|
|
44
|
-
expect(result).toContain('Google Calendar');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('creates event through Google Calendar API', async () => {
|
|
48
|
-
readConfig.mockReturnValue({
|
|
49
|
-
googleCalendarClientId: 'client-id',
|
|
50
|
-
googleCalendarClientSecret: 'client-secret',
|
|
51
|
-
googleCalendarRefreshToken: 'refresh-token',
|
|
52
|
-
googleCalendarId: 'primary'
|
|
53
|
-
});
|
|
54
|
-
axios.post
|
|
55
|
-
.mockResolvedValueOnce({ data: { access_token: 'access-token' } })
|
|
56
|
-
.mockResolvedValueOnce({
|
|
57
|
-
data: {
|
|
58
|
-
summary: 'Demo meeting',
|
|
59
|
-
htmlLink: 'https://calendar.google.com/event/demo'
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const result = await plugin.execute(JSON.stringify({
|
|
64
|
-
action: 'create',
|
|
65
|
-
summary: 'Demo meeting',
|
|
66
|
-
start: '2026-05-15T10:00:00+07:00',
|
|
67
|
-
end: '2026-05-15T11:00:00+07:00'
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
expect(axios.post).toHaveBeenCalledTimes(2);
|
|
71
|
-
expect(axios.post.mock.calls[1][0]).toContain('/calendars/primary/events');
|
|
72
|
-
expect(axios.post.mock.calls[1][1].summary).toBe('Demo meeting');
|
|
73
|
-
expect(result).toContain('Demo meeting');
|
|
74
|
-
expect(result).toContain('https://calendar.google.com/event/demo');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('lists upcoming events through Google Calendar API', async () => {
|
|
78
|
-
readConfig.mockReturnValue({
|
|
79
|
-
googleCalendarClientId: 'client-id',
|
|
80
|
-
googleCalendarClientSecret: 'client-secret',
|
|
81
|
-
googleCalendarRefreshToken: 'refresh-token',
|
|
82
|
-
googleCalendarId: 'primary'
|
|
83
|
-
});
|
|
84
|
-
axios.post.mockResolvedValueOnce({ data: { access_token: 'access-token' } });
|
|
85
|
-
axios.get.mockResolvedValueOnce({
|
|
86
|
-
data: {
|
|
87
|
-
items: [
|
|
88
|
-
{
|
|
89
|
-
summary: 'Planning',
|
|
90
|
-
start: { dateTime: '2026-05-15T10:00:00+07:00' },
|
|
91
|
-
end: { dateTime: '2026-05-15T11:00:00+07:00' }
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const result = await plugin.execute(JSON.stringify({ action: 'list', days: 3 }));
|
|
98
|
-
|
|
99
|
-
expect(axios.get).toHaveBeenCalledTimes(1);
|
|
100
|
-
expect(axios.get.mock.calls[0][0]).toContain('/calendars/primary/events');
|
|
101
|
-
expect(result).toContain('Planning');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('builds all-day event payload with exclusive end date', () => {
|
|
105
|
-
const payload = plugin._helpers.buildEventPayload({
|
|
106
|
-
summary: 'All day',
|
|
107
|
-
date: '2026-05-15'
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(payload.start).toEqual({ date: '2026-05-15' });
|
|
111
|
-
expect(payload.end).toEqual({ date: '2026-05-16' });
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const { getGoogleTtsUrls, splitTextForTts } = require('../src/System/google_tts_urls');
|
|
2
|
-
|
|
3
|
-
describe('google_tts_urls', () => {
|
|
4
|
-
test('returns no URLs for empty text', () => {
|
|
5
|
-
expect(getGoogleTtsUrls('')).toEqual([]);
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
test('builds Google Translate TTS URLs with encoded text', () => {
|
|
9
|
-
const urls = getGoogleTtsUrls('hello world', { lang: 'en' });
|
|
10
|
-
|
|
11
|
-
expect(urls).toHaveLength(1);
|
|
12
|
-
expect(urls[0].shortText).toBe('hello world');
|
|
13
|
-
expect(urls[0].url).toContain('translate_tts?');
|
|
14
|
-
expect(urls[0].url).toContain('q=hello+world');
|
|
15
|
-
expect(urls[0].url).toContain('tl=en');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('splits long text into bounded chunks', () => {
|
|
19
|
-
const chunks = splitTextForTts('a '.repeat(150), 50);
|
|
20
|
-
|
|
21
|
-
expect(chunks.length).toBeGreaterThan(1);
|
|
22
|
-
expect(chunks.every(chunk => chunk.length <= 50)).toBe(true);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests: memory_store.js
|
|
3
|
-
* Tests profile CRUD, usage pattern recording, and getUserContext output.
|
|
4
|
-
* Uses an isolated in-memory DB path via MINT_TEST_DB env var.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
|
|
11
|
-
// ── Isolate DB per test run ─────────────────────────────────────────────────
|
|
12
|
-
let tempDir;
|
|
13
|
-
let memStore;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
jest.resetModules();
|
|
17
|
-
|
|
18
|
-
// Create isolated temp directory
|
|
19
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-mem-test-'));
|
|
20
|
-
|
|
21
|
-
// Patch os.homedir so memory_store uses our temp .mint dir
|
|
22
|
-
jest.spyOn(os, 'homedir').mockReturnValue(tempDir);
|
|
23
|
-
|
|
24
|
-
// Require fresh instance (no cache)
|
|
25
|
-
memStore = require('../src/AI_Brain/memory_store');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
jest.restoreAllMocks();
|
|
30
|
-
jest.resetModules();
|
|
31
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// ── Profile tests ──────────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
describe('memory_store — setProfile / getProfile', () => {
|
|
37
|
-
test('stores and retrieves a string value', () => {
|
|
38
|
-
memStore.setProfile('test_key', 'hello');
|
|
39
|
-
expect(memStore.getProfile('test_key')).toBe('hello');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('returns defaultValue when key does not exist', () => {
|
|
43
|
-
expect(memStore.getProfile('nonexistent', 'default_val')).toBe('default_val');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('overwrites existing key', () => {
|
|
47
|
-
memStore.setProfile('key', 'first');
|
|
48
|
-
memStore.setProfile('key', 'second');
|
|
49
|
-
expect(memStore.getProfile('key')).toBe('second');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('stores numeric value as string', () => {
|
|
53
|
-
memStore.setProfile('count', 42);
|
|
54
|
-
expect(memStore.getProfile('count')).toBe('42');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ── recordInteraction tests ────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
describe('memory_store — recordInteraction', () => {
|
|
61
|
-
test('records preferred language as thai for thai text', () => {
|
|
62
|
-
memStore.recordInteraction('ช่วยเขียนโค้ดให้หน่อยนะคะ', 'โค้ดค่ะ');
|
|
63
|
-
expect(memStore.getProfile('preferred_language')).toBe('thai');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('records preferred language as english for english text', () => {
|
|
67
|
-
memStore.recordInteraction('please help me write a function', 'sure!');
|
|
68
|
-
expect(memStore.getProfile('preferred_language')).toBe('english');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('increments total_interactions counter', () => {
|
|
72
|
-
memStore.recordInteraction('msg 1', 'reply 1');
|
|
73
|
-
memStore.recordInteraction('msg 2', 'reply 2');
|
|
74
|
-
expect(memStore.getProfile('total_interactions')).toBe('2');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('records coding keywords as patterns', () => {
|
|
78
|
-
memStore.recordInteraction('fix the Python script bug', 'sure!');
|
|
79
|
-
const patterns = memStore.getTopPatterns(10).map(p => p.pattern);
|
|
80
|
-
// 'python' and 'script' are keywords extracted (stop words removed)
|
|
81
|
-
expect(patterns.some(p => ['python', 'script', 'fix'].includes(p))).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('does not throw on empty message', () => {
|
|
85
|
-
expect(() => memStore.recordInteraction('', '')).not.toThrow();
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ── getTopPatterns tests ───────────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
describe('memory_store — getTopPatterns', () => {
|
|
92
|
-
test('returns patterns sorted by count descending', () => {
|
|
93
|
-
memStore.recordInteraction('python python python', 'ok');
|
|
94
|
-
memStore.recordInteraction('python javascript', 'ok');
|
|
95
|
-
const patterns = memStore.getTopPatterns(5);
|
|
96
|
-
const patternNames = patterns.map(p => p.pattern);
|
|
97
|
-
expect(patternNames[0]).toBe('python'); // python used most
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('returns empty array when no interactions recorded', () => {
|
|
101
|
-
expect(memStore.getTopPatterns(5)).toEqual([]);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// ── getUserContext tests ───────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
describe('memory_store — getUserContext', () => {
|
|
108
|
-
test('returns empty string when no data', () => {
|
|
109
|
-
expect(memStore.getUserContext()).toBe('');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('includes preferred_language in context output', () => {
|
|
113
|
-
memStore.recordInteraction('สวัสดีครับ', 'สวัสดีค่ะ');
|
|
114
|
-
const ctx = memStore.getUserContext();
|
|
115
|
-
expect(ctx).toContain('thai');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('includes total_interactions in context', () => {
|
|
119
|
-
memStore.recordInteraction('hello', 'hi');
|
|
120
|
-
const ctx = memStore.getUserContext();
|
|
121
|
-
expect(ctx).toContain('1');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('context starts with the user context header', () => {
|
|
125
|
-
memStore.setProfile('preferred_language', 'english');
|
|
126
|
-
const ctx = memStore.getUserContext();
|
|
127
|
-
expect(ctx).toContain('[LONG-TERM USER CONTEXT');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('context ends with END marker', () => {
|
|
131
|
-
memStore.setProfile('preferred_language', 'thai');
|
|
132
|
-
const ctx = memStore.getUserContext();
|
|
133
|
-
expect(ctx).toContain('[END USER CONTEXT]');
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ── Response Cache tests ──────────────────────────────────────────────────
|
|
138
|
-
|
|
139
|
-
describe('memory_store — response cache', () => {
|
|
140
|
-
test('stores and retrieves a response', () => {
|
|
141
|
-
const query = 'What is 2+2?';
|
|
142
|
-
const response = { response: '4', action: { type: 'none' } };
|
|
143
|
-
memStore.cacheResponse(query, response);
|
|
144
|
-
|
|
145
|
-
const cached = memStore.getCachedResponse(query);
|
|
146
|
-
expect(cached.response).toBe('4');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('returns null for missing query', () => {
|
|
150
|
-
expect(memStore.getCachedResponse('where is my cat?')).toBe(null);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('is case-insensitive and ignores whitespace', () => {
|
|
154
|
-
const query = ' Hello World ';
|
|
155
|
-
const response = { response: 'Hi!', action: { type: 'none' } };
|
|
156
|
-
memStore.cacheResponse(query, response);
|
|
157
|
-
|
|
158
|
-
const cached = memStore.getCachedResponse('hello world');
|
|
159
|
-
expect(cached.response).toBe('Hi!');
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ── saveSessionSummary / getRecentMemories tests ───────────────────────────
|
|
164
|
-
|
|
165
|
-
describe('memory_store — session summaries', () => {
|
|
166
|
-
test('saves and retrieves a summary', () => {
|
|
167
|
-
memStore.saveSessionSummary('User asked about Python scripting', ['python', 'coding']);
|
|
168
|
-
const memories = memStore.getRecentMemories(5);
|
|
169
|
-
expect(memories.length).toBe(1);
|
|
170
|
-
expect(memories[0].summary).toBe('User asked about Python scripting');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('ignores too-short summaries', () => {
|
|
174
|
-
memStore.saveSessionSummary('ok', []);
|
|
175
|
-
const memories = memStore.getRecentMemories(5);
|
|
176
|
-
expect(memories.length).toBe(0);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('returns most recent summaries first', () => {
|
|
180
|
-
memStore.saveSessionSummary('First session about web development', []);
|
|
181
|
-
memStore.saveSessionSummary('Second session about machine learning', []);
|
|
182
|
-
const memories = memStore.getRecentMemories(5);
|
|
183
|
-
expect(memories[0].summary).toContain('Second');
|
|
184
|
-
});
|
|
185
|
-
});
|