@pheem49/mint 1.4.1 → 1.5.0

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 (61) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +214 -142
  3. package/assets/CLI_Screen.png +0 -0
  4. package/docs/assets/CLI_Screen.png +0 -0
  5. package/docs/guide.html +632 -0
  6. package/docs/index.html +5 -4
  7. package/main.js +66 -894
  8. package/mint-cli-logic.js +15 -8
  9. package/mint-cli.js +305 -195
  10. package/package.json +12 -4
  11. package/src/AI_Brain/Gemini_API.js +77 -20
  12. package/src/AI_Brain/agent_orchestrator.js +6 -6
  13. package/src/AI_Brain/autonomous_brain.js +10 -0
  14. package/src/AI_Brain/behavior_memory.js +26 -5
  15. package/src/AI_Brain/headless_agent.js +4 -0
  16. package/src/AI_Brain/knowledge_base.js +61 -8
  17. package/src/AI_Brain/memory_store.js +55 -7
  18. package/src/Automation_Layer/file_operations.js +14 -3
  19. package/src/CLI/chat_router.js +21 -7
  20. package/src/CLI/chat_ui.js +264 -710
  21. package/src/CLI/code_agent.js +370 -124
  22. package/src/CLI/gmail_auth.js +210 -0
  23. package/src/CLI/list_features.js +5 -1
  24. package/src/CLI/onboarding.js +307 -55
  25. package/src/CLI/updater.js +208 -0
  26. package/src/Channels/brave_search_bridge.js +35 -0
  27. package/src/Channels/discord_bridge.js +68 -0
  28. package/src/Channels/google_search_bridge.js +38 -0
  29. package/src/Channels/line_bridge.js +60 -0
  30. package/src/Channels/slack_bridge.js +53 -0
  31. package/src/Channels/telegram_bridge.js +49 -0
  32. package/src/Channels/whatsapp_bridge.js +55 -0
  33. package/src/Command_Parser/parser.js +12 -1
  34. package/src/Plugins/gmail.js +251 -0
  35. package/src/Plugins/google_calendar.js +245 -19
  36. package/src/Plugins/notion.js +256 -0
  37. package/src/System/action_executor.js +129 -0
  38. package/src/System/bridge_manager.js +76 -0
  39. package/src/System/chat_history_manager.js +23 -5
  40. package/src/System/config_manager.js +41 -7
  41. package/src/System/custom_workflows.js +31 -2
  42. package/src/System/google_tts_urls.js +51 -0
  43. package/src/System/ipc_handlers.js +238 -0
  44. package/src/System/proactive_loop.js +137 -0
  45. package/src/System/safety_manager.js +165 -0
  46. package/src/System/screen_capture.js +175 -0
  47. package/src/System/task_manager.js +15 -5
  48. package/src/System/window_manager.js +210 -0
  49. package/src/UI/renderer.js +33 -7
  50. package/src/UI/settings.html +24 -0
  51. package/src/UI/settings.js +14 -4
  52. package/src/UI/styles.css +14 -1
  53. package/tests/action_executor_safety.test.js +67 -0
  54. package/tests/gmail.test.js +135 -0
  55. package/tests/gmail_auth.test.js +129 -0
  56. package/tests/google_calendar.test.js +113 -0
  57. package/tests/google_tts_urls.test.js +24 -0
  58. package/tests/notion.test.js +121 -0
  59. package/tests/provider_routing.test.js +17 -1
  60. package/tests/safety_manager.test.js +40 -0
  61. package/tests/updater.test.js +32 -0
@@ -0,0 +1,210 @@
1
+ const http = require('http');
2
+ const { execFile } = require('child_process');
3
+ const crypto = require('crypto');
4
+ const axios = require('axios');
5
+ const { readConfig, writeConfig } = require('../System/config_manager');
6
+
7
+ const TOKEN_URL = 'https://oauth2.googleapis.com/token';
8
+ const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
9
+ const DEFAULT_SCOPES = [
10
+ 'https://www.googleapis.com/auth/gmail.readonly',
11
+ 'https://www.googleapis.com/auth/gmail.compose'
12
+ ];
13
+
14
+ function buildRedirectUri(port) {
15
+ return `http://127.0.0.1:${port}/oauth2callback`;
16
+ }
17
+
18
+ function buildAuthUrl({ clientId, redirectUri, state, scopes = DEFAULT_SCOPES }) {
19
+ const params = new URLSearchParams({
20
+ client_id: clientId,
21
+ redirect_uri: redirectUri,
22
+ response_type: 'code',
23
+ scope: scopes.join(' '),
24
+ access_type: 'offline',
25
+ prompt: 'consent',
26
+ state
27
+ });
28
+
29
+ return `${AUTH_URL}?${params.toString()}`;
30
+ }
31
+
32
+ function openBrowser(url) {
33
+ const command = process.platform === 'darwin'
34
+ ? 'open'
35
+ : process.platform === 'win32'
36
+ ? 'cmd'
37
+ : 'xdg-open';
38
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
39
+
40
+ return new Promise((resolve, reject) => {
41
+ execFile(command, args, (error) => {
42
+ if (error) {
43
+ reject(error);
44
+ return;
45
+ }
46
+ resolve();
47
+ });
48
+ });
49
+ }
50
+
51
+ async function exchangeCodeForToken({ clientId, clientSecret, code, redirectUri }) {
52
+ const params = new URLSearchParams({
53
+ client_id: clientId,
54
+ client_secret: clientSecret,
55
+ code,
56
+ redirect_uri: redirectUri,
57
+ grant_type: 'authorization_code'
58
+ });
59
+
60
+ const response = await axios.post(TOKEN_URL, params.toString(), {
61
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
62
+ });
63
+
64
+ return response.data;
65
+ }
66
+
67
+ function waitForOAuthCode({ port = 0, state, timeoutMs = 180000 }) {
68
+ return new Promise((resolve, reject) => {
69
+ let settled = false;
70
+ let timer = null;
71
+
72
+ const finish = (error, value) => {
73
+ if (settled) return;
74
+ settled = true;
75
+ if (timer) clearTimeout(timer);
76
+ server.close(() => {
77
+ if (error) reject(error);
78
+ else resolve(value);
79
+ });
80
+ };
81
+
82
+ const server = http.createServer((req, res) => {
83
+ try {
84
+ const url = new URL(req.url, 'http://127.0.0.1');
85
+ if (url.pathname !== '/oauth2callback') {
86
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
87
+ res.end('Not found');
88
+ return;
89
+ }
90
+
91
+ const returnedState = url.searchParams.get('state');
92
+ const error = url.searchParams.get('error');
93
+ const code = url.searchParams.get('code');
94
+
95
+ if (error) {
96
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
97
+ res.end(`Gmail authorization failed: ${error}`);
98
+ finish(new Error(`Gmail authorization failed: ${error}`));
99
+ return;
100
+ }
101
+
102
+ if (!code || returnedState !== state) {
103
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
104
+ res.end('Invalid Gmail authorization response.');
105
+ finish(new Error('Invalid Gmail authorization response.'));
106
+ return;
107
+ }
108
+
109
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
110
+ res.end('<h1>Gmail connected</h1><p>You can close this window and return to Mint.</p>');
111
+ finish(null, code);
112
+ } catch (err) {
113
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
114
+ res.end('Internal error.');
115
+ finish(err);
116
+ }
117
+ });
118
+
119
+ server.on('error', finish);
120
+ server.listen(port, '127.0.0.1', () => {
121
+ timer = setTimeout(() => {
122
+ finish(new Error('Timed out waiting for Gmail authorization callback.'));
123
+ }, timeoutMs);
124
+ });
125
+ });
126
+ }
127
+
128
+ async function runGmailAuth(options = {}) {
129
+ const logger = options.logger || console;
130
+ const config = options.readConfig ? options.readConfig() : readConfig();
131
+ const clientId = (config.gmailClientId || '').trim();
132
+ const clientSecret = (config.gmailClientSecret || '').trim();
133
+ const userId = (config.gmailUserId || 'me').trim() || 'me';
134
+
135
+ if (!clientId || !clientSecret) {
136
+ throw new Error('Missing Gmail OAuth Client ID or Client Secret. Run `mint onboard` and fill Gmail API credentials first.');
137
+ }
138
+
139
+ const state = crypto.randomBytes(16).toString('hex');
140
+ const actualPort = options.getAuthorizationCode ? Number(options.port || 8787) : await reserveLocalPort(Number(options.port || 0));
141
+ const redirectUri = buildRedirectUri(actualPort);
142
+ const codePromise = options.getAuthorizationCode
143
+ ? null
144
+ : waitForOAuthCode({
145
+ port: actualPort,
146
+ state,
147
+ timeoutMs: options.timeoutMs || 180000
148
+ });
149
+ const authUrl = buildAuthUrl({ clientId, redirectUri, state, scopes: options.scopes || DEFAULT_SCOPES });
150
+
151
+ logger.log(`Open this Google OAuth consent link for Gmail (${userId}):\n${authUrl}\n`);
152
+
153
+ if (options.openBrowser !== false) {
154
+ const browserOpener = options.openBrowser || openBrowser;
155
+ await browserOpener(authUrl);
156
+ }
157
+
158
+ const code = options.getAuthorizationCode
159
+ ? await options.getAuthorizationCode({ authUrl, state, redirectUri })
160
+ : await codePromise;
161
+ const token = await exchangeCodeForToken({
162
+ clientId,
163
+ clientSecret,
164
+ code,
165
+ redirectUri
166
+ });
167
+
168
+ if (!token.refresh_token) {
169
+ throw new Error('Google did not return a refresh token. Re-run `mint gmail auth`; the flow uses prompt=consent to request one.');
170
+ }
171
+
172
+ const nextConfig = {
173
+ ...config,
174
+ gmailRefreshToken: token.refresh_token,
175
+ gmailUserId: userId,
176
+ pluginGmailEnabled: true
177
+ };
178
+
179
+ const writeResult = options.writeConfig ? options.writeConfig(nextConfig) : writeConfig(nextConfig);
180
+ if (writeResult && writeResult.success === false) {
181
+ throw new Error(writeResult.message || 'Failed to save Gmail refresh token.');
182
+ }
183
+
184
+ return {
185
+ success: true,
186
+ userId,
187
+ scopes: options.scopes || DEFAULT_SCOPES
188
+ };
189
+ }
190
+
191
+ function reserveLocalPort(port = 0) {
192
+ return new Promise((resolve, reject) => {
193
+ const server = http.createServer();
194
+ server.on('error', reject);
195
+ server.listen(port, '127.0.0.1', () => {
196
+ const actualPort = server.address().port;
197
+ server.close(() => resolve(actualPort));
198
+ });
199
+ });
200
+ }
201
+
202
+ module.exports = {
203
+ DEFAULT_SCOPES,
204
+ buildRedirectUri,
205
+ buildAuthUrl,
206
+ exchangeCodeForToken,
207
+ waitForOAuthCode,
208
+ reserveLocalPort,
209
+ runGmailAuth
210
+ };
@@ -22,11 +22,15 @@ function displayFeatures() {
22
22
  const commands = [
23
23
  { cmd: 'mint', desc: 'Start interactive chat session (Default)' },
24
24
  { cmd: 'mint code "<task>"', desc: 'Run workspace-aware coding agent in current directory' },
25
+ { cmd: 'mint gmail auth', desc: 'Connect Gmail OAuth and save refresh token' },
26
+ { cmd: 'mint mcp', desc: 'Manage Model Context Protocol (MCP) servers' },
27
+ { cmd: 'mint task "<task>"', desc: 'Queue an autonomous task for the background agent' },
28
+ { cmd: 'mint update', desc: 'Check for and install the latest Mint CLI version' },
25
29
  { cmd: 'mint onboard', desc: 'Run setup wizard (API Key, Model, Daemon)' },
26
30
  { cmd: 'mint agent', desc: 'Run Mint as a background agent (Headless)' },
27
31
  { cmd: 'mint list', desc: 'Show this features & commands list' }
28
32
  ];
29
- commands.forEach(c => console.log(` - ${colors.cyan}${c.cmd.padEnd(15)}${colors.reset} : ${c.desc}`));
33
+ commands.forEach(c => console.log(` - ${colors.cyan}${c.cmd.padEnd(18)}${colors.reset} : ${c.desc}`));
30
34
 
31
35
  console.log(`\n${colors.bright}AI Core Actions (Automation):${colors.reset}`);
32
36
  const actions = [
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { readConfig, writeConfig } = require('../System/config_manager');
4
4
  const { installDaemon } = require('../System/daemon_manager');
5
+ const { runGmailAuth } = require('./gmail_auth');
5
6
 
6
7
  /**
7
8
  * Onboarding Wizard for Mint CLI
@@ -12,84 +13,335 @@ async function runOnboarding(options = {}) {
12
13
 
13
14
  console.log('\nWelcome to Mint Onboarding! Let\'s get you set up.\n');
14
15
 
15
- const config = readConfig();
16
+ let config = readConfig();
16
17
 
17
- const questions = [
18
+ // 1. Basic Setup (Gemini is mandatory for core features)
19
+ const basicAnswers = await inquirer.prompt([
18
20
  {
19
21
  type: 'input',
20
22
  name: 'apiKey',
21
- message: 'Enter your Google Gemini API Key (Required for basic features):',
23
+ message: 'Enter your Google Gemini API Key:',
22
24
  default: config.apiKey || undefined,
23
25
  validate: (input) => input.trim().length > 0 ? true : 'API Key is required.'
24
26
  },
25
27
  {
26
28
  type: 'list',
27
- name: 'geminiModelChoice',
28
- message: 'Select the primary Gemini model to use:',
29
+ name: 'geminiModel',
30
+ message: 'Select primary Gemini model:',
29
31
  choices: [
30
32
  'gemini-2.5-flash',
31
- 'gemini-2.0-pro-exp-02-05',
32
33
  'gemini-3.1-flash-lite-preview',
33
- 'gemini-3.1-flash-lite',
34
- 'Custom model name'
34
+ 'gemini-1.5-pro'
35
35
  ],
36
36
  default: config.geminiModel || 'gemini-2.5-flash'
37
- },
38
- {
39
- type: 'input',
40
- name: 'customGeminiModel',
41
- message: 'Enter your custom Gemini model name:',
42
- when: (answers) => answers.geminiModelChoice === 'Custom model name',
43
- validate: (input) => input.trim().length > 0 ? true : 'Please enter a valid model name.'
44
- },
45
- {
46
- type: 'input',
47
- name: 'anthropicApiKey',
48
- message: 'Enter your Anthropic API Key (Optional, press Enter to skip):',
49
- default: config.anthropicApiKey || ''
50
- },
51
- {
52
- type: 'input',
53
- name: 'openaiApiKey',
54
- message: 'Enter your OpenAI API Key (Optional, press Enter to skip):',
55
- default: config.openaiApiKey || ''
56
- },
57
- {
58
- type: 'input',
59
- name: 'hfApiKey',
60
- message: 'Enter your Hugging Face API Key (Optional, press Enter to skip):',
61
- default: config.hfApiKey || ''
62
- },
63
- {
64
- type: 'input',
65
- name: 'localApiBaseUrl',
66
- message: 'Enter your Local AI (LM Studio/OpenAI Compatible) Base URL (Optional, press Enter to skip):',
67
- default: config.localApiBaseUrl || ''
68
- },
37
+ }
38
+ ]);
39
+
40
+ config = { ...config, ...basicAnswers };
41
+
42
+ // 2. Interactive Channel/Provider Selection (QuickStart Style)
43
+ const { selections } = await inquirer.prompt([
69
44
  {
70
- type: 'input',
71
- name: 'localModelName',
72
- message: 'Enter your Local Model Name (Optional, press Enter to skip):',
73
- default: config.localModelName || ''
45
+ type: 'checkbox',
46
+ name: 'selections',
47
+ message: 'Select channels/providers to configure (QuickStart):',
48
+ pageSize: 20,
49
+ choices: [
50
+ { name: 'Telegram (Bot API)', value: 'telegram', checked: config.enableTelegramBridge },
51
+ { name: 'WhatsApp (QR link)', value: 'whatsapp', checked: config.enableWhatsappBridge },
52
+ { name: 'Discord (Bot API)', value: 'discord', checked: config.enableDiscordBridge },
53
+ { name: 'Slack (Socket Mode)', value: 'slack', checked: config.enableSlackBridge },
54
+ { name: 'LINE (Messaging API)', value: 'line', checked: config.enableLineBridge },
55
+ { name: 'Google Calendar API', value: 'google_calendar', checked: config.pluginCalendarEnabled },
56
+ { name: 'Gmail API', value: 'gmail', checked: config.pluginGmailEnabled },
57
+ { name: 'Notion API', value: 'notion', checked: config.pluginNotionEnabled },
58
+ new inquirer.Separator(),
59
+ { name: 'Anthropic (Claude)', value: 'anthropic', checked: config.aiProvider === 'anthropic' || !!config.anthropicApiKey },
60
+ { name: 'OpenAI (GPT-4o)', value: 'openai', checked: config.aiProvider === 'openai' || !!config.openaiApiKey },
61
+ { name: 'Hugging Face', value: 'hf', checked: config.aiProvider === 'huggingface' || !!config.hfApiKey },
62
+ { name: 'Local AI (LM Studio/Ollama)', value: 'local', checked: config.aiProvider === 'local_openai' || (!!config.localApiBaseUrl && config.localApiBaseUrl.length > 0) },
63
+ new inquirer.Separator(),
64
+ { name: 'Google Search API', value: 'google_search', checked: !!config.googleSearchApiKey },
65
+ { name: 'Brave Search API', value: 'brave_search', checked: !!config.braveSearchApiKey },
66
+ new inquirer.Separator(),
67
+ { name: 'Skip for now', value: 'skip' }
68
+ ]
74
69
  }
75
- ];
70
+ ]);
76
71
 
77
- const answers = await inquirer.prompt(questions);
72
+ // 3. Configure selected items
73
+ const dynamicQuestions = [];
78
74
 
79
- // Resolve custom gemini model if selected
80
- const geminiModel = answers.geminiModelChoice === 'Custom model name'
81
- ? answers.customGeminiModel
82
- : answers.geminiModelChoice;
75
+ // Reset enabled flags if we are not skipping
76
+ if (!selections.includes('skip')) {
77
+ config.enableTelegramBridge = selections.includes('telegram');
78
+ config.enableWhatsappBridge = selections.includes('whatsapp');
79
+ config.enableDiscordBridge = selections.includes('discord');
80
+ config.enableSlackBridge = selections.includes('slack');
81
+ config.enableLineBridge = selections.includes('line');
82
+ }
83
+
84
+ // If "Skip for now" is selected or nothing is selected, we move to save
85
+ if (selections.includes('skip')) {
86
+ console.log('\nā© Skipping optional configuration...');
87
+ } else {
88
+ if (selections.includes('google_search')) {
89
+ dynamicQuestions.push({
90
+ type: 'input',
91
+ name: 'googleSearchApiKey',
92
+ message: 'Enter Google Search API Key:',
93
+ default: config.googleSearchApiKey
94
+ });
95
+ dynamicQuestions.push({
96
+ type: 'input',
97
+ name: 'googleSearchCx',
98
+ message: 'Enter Google Search CX (Engine ID):',
99
+ default: config.googleSearchCx
100
+ });
101
+ }
83
102
 
84
- // Remove temporary choice fields before saving
85
- delete answers.geminiModelChoice;
86
- delete answers.customGeminiModel;
103
+ if (selections.includes('brave_search')) {
104
+ dynamicQuestions.push({
105
+ type: 'input',
106
+ name: 'braveSearchApiKey',
107
+ message: 'Enter Brave Search API Key:',
108
+ default: config.braveSearchApiKey
109
+ });
110
+ }
111
+ if (selections.includes('discord')) {
112
+ dynamicQuestions.push({
113
+ type: 'input',
114
+ name: 'discordBotToken',
115
+ message: 'Enter Discord Bot Token:',
116
+ default: config.discordBotToken
117
+ });
118
+ }
119
+
120
+ if (selections.includes('telegram')) {
121
+ dynamicQuestions.push({
122
+ type: 'input',
123
+ name: 'telegramBotToken',
124
+ message: 'Enter Telegram Bot Token:',
125
+ default: config.telegramBotToken
126
+ });
127
+ }
128
+
129
+ if (selections.includes('slack')) {
130
+ dynamicQuestions.push({
131
+ type: 'input',
132
+ name: 'slackBotToken',
133
+ message: 'Enter Slack Bot Token (xoxb-...):',
134
+ default: config.slackBotToken
135
+ });
136
+ dynamicQuestions.push({
137
+ type: 'input',
138
+ name: 'slackAppToken',
139
+ message: 'Enter Slack App Token (xapp-...):',
140
+ default: config.slackAppToken
141
+ });
142
+ }
143
+
144
+ if (selections.includes('line')) {
145
+ dynamicQuestions.push({
146
+ type: 'input',
147
+ name: 'lineChannelAccessToken',
148
+ message: 'Enter LINE Channel Access Token:',
149
+ default: config.lineChannelAccessToken
150
+ });
151
+ dynamicQuestions.push({
152
+ type: 'input',
153
+ name: 'lineChannelSecret',
154
+ message: 'Enter LINE Channel Secret:',
155
+ default: config.lineChannelSecret
156
+ });
157
+ dynamicQuestions.push({
158
+ type: 'number',
159
+ name: 'lineWebhookPort',
160
+ message: 'Enter LINE Webhook Port (Local):',
161
+ default: config.lineWebhookPort || 3000
162
+ });
163
+ }
164
+
165
+ if (selections.includes('google_calendar')) {
166
+ dynamicQuestions.push({
167
+ type: 'input',
168
+ name: 'googleCalendarClientId',
169
+ message: 'Enter Google Calendar OAuth Client ID:',
170
+ default: config.googleCalendarClientId
171
+ });
172
+ dynamicQuestions.push({
173
+ type: 'input',
174
+ name: 'googleCalendarClientSecret',
175
+ message: 'Enter Google Calendar OAuth Client Secret:',
176
+ default: config.googleCalendarClientSecret
177
+ });
178
+ dynamicQuestions.push({
179
+ type: 'input',
180
+ name: 'googleCalendarRefreshToken',
181
+ message: 'Enter Google Calendar Refresh Token:',
182
+ default: config.googleCalendarRefreshToken
183
+ });
184
+ dynamicQuestions.push({
185
+ type: 'input',
186
+ name: 'googleCalendarId',
187
+ message: 'Enter Google Calendar ID:',
188
+ default: config.googleCalendarId || 'primary'
189
+ });
190
+ config.pluginCalendarEnabled = true;
191
+ } else {
192
+ config.pluginCalendarEnabled = false;
193
+ }
194
+
195
+ if (selections.includes('gmail')) {
196
+ dynamicQuestions.push({
197
+ type: 'input',
198
+ name: 'gmailClientId',
199
+ message: 'Enter Gmail OAuth Client ID:',
200
+ default: config.gmailClientId
201
+ });
202
+ dynamicQuestions.push({
203
+ type: 'input',
204
+ name: 'gmailClientSecret',
205
+ message: 'Enter Gmail OAuth Client Secret:',
206
+ default: config.gmailClientSecret
207
+ });
208
+ dynamicQuestions.push({
209
+ type: 'input',
210
+ name: 'gmailRefreshToken',
211
+ message: 'Enter Gmail Refresh Token:',
212
+ default: config.gmailRefreshToken
213
+ });
214
+ dynamicQuestions.push({
215
+ type: 'input',
216
+ name: 'gmailUserId',
217
+ message: 'Enter Gmail User ID:',
218
+ default: config.gmailUserId || 'me'
219
+ });
220
+ config.pluginGmailEnabled = true;
221
+ } else {
222
+ config.pluginGmailEnabled = false;
223
+ }
224
+
225
+ if (selections.includes('notion')) {
226
+ dynamicQuestions.push({
227
+ type: 'input',
228
+ name: 'notionApiKey',
229
+ message: 'Enter Notion Internal Integration Secret:',
230
+ default: config.notionApiKey
231
+ });
232
+ dynamicQuestions.push({
233
+ type: 'input',
234
+ name: 'notionDatabaseId',
235
+ message: 'Enter default Notion Database ID (optional):',
236
+ default: config.notionDatabaseId
237
+ });
238
+ dynamicQuestions.push({
239
+ type: 'input',
240
+ name: 'notionPageId',
241
+ message: 'Enter default Notion Page ID (optional):',
242
+ default: config.notionPageId
243
+ });
244
+ dynamicQuestions.push({
245
+ type: 'input',
246
+ name: 'notionTitleProperty',
247
+ message: 'Enter database title property name:',
248
+ default: config.notionTitleProperty || 'Name'
249
+ });
250
+ config.pluginNotionEnabled = true;
251
+ } else {
252
+ config.pluginNotionEnabled = false;
253
+ }
254
+
255
+ if (selections.includes('anthropic')) {
256
+ dynamicQuestions.push({
257
+ type: 'input',
258
+ name: 'anthropicApiKey',
259
+ message: 'Enter Anthropic API Key:',
260
+ default: config.anthropicApiKey
261
+ });
262
+ }
263
+
264
+ if (selections.includes('openai')) {
265
+ dynamicQuestions.push({
266
+ type: 'input',
267
+ name: 'openaiApiKey',
268
+ message: 'Enter OpenAI API Key:',
269
+ default: config.openaiApiKey
270
+ });
271
+ }
272
+
273
+ if (selections.includes('hf')) {
274
+ dynamicQuestions.push({
275
+ type: 'input',
276
+ name: 'hfApiKey',
277
+ message: 'Enter Hugging Face API Key:',
278
+ default: config.hfApiKey
279
+ });
280
+ }
281
+
282
+ if (selections.includes('local')) {
283
+ dynamicQuestions.push({
284
+ type: 'input',
285
+ name: 'localApiBaseUrl',
286
+ message: 'Enter Local AI Base URL:',
287
+ default: config.localApiBaseUrl || 'http://localhost:1234/v1'
288
+ });
289
+ dynamicQuestions.push({
290
+ type: 'input',
291
+ name: 'localModelName',
292
+ message: 'Enter Local Model Name:',
293
+ default: config.localModelName || 'local-model'
294
+ });
295
+ }
296
+ }
297
+
298
+ if (dynamicQuestions.length > 0) {
299
+ const extraAnswers = await inquirer.prompt(dynamicQuestions);
300
+ config = { ...config, ...extraAnswers };
301
+
302
+ }
303
+
304
+ // Ensure aiProvider reflects the selected primary AI. If no optional AI
305
+ // provider is selected, keep Gemini as the safe default from basic setup.
306
+ if (!selections.includes('skip')) {
307
+ if (selections.includes('anthropic')) config.aiProvider = 'anthropic';
308
+ else if (selections.includes('openai')) config.aiProvider = 'openai';
309
+ else if (selections.includes('hf')) config.aiProvider = 'huggingface';
310
+ else if (selections.includes('local')) config.aiProvider = 'local_openai';
311
+ else config.aiProvider = 'gemini';
312
+ }
87
313
 
88
314
  // Save configuration
89
- const newConfig = { ...config, ...answers, geminiModel };
90
- writeConfig(newConfig);
315
+ writeConfig(config);
91
316
  console.log('\nāœ… Configuration saved successfully!');
92
317
 
318
+ if (!selections.includes('skip') && selections.includes('gmail') && !config.gmailRefreshToken) {
319
+ const { runGmailAuthNow } = await inquirer.prompt([
320
+ {
321
+ type: 'confirm',
322
+ name: 'runGmailAuthNow',
323
+ message: 'Gmail Refresh Token is empty. Start Gmail OAuth now?',
324
+ default: true
325
+ }
326
+ ]);
327
+
328
+ if (runGmailAuthNow) {
329
+ console.log('\nšŸ” Starting Gmail OAuth. Open the link below, sign in, and approve access.');
330
+ try {
331
+ const result = await runGmailAuth({
332
+ logger: console,
333
+ openBrowser: false
334
+ });
335
+ console.log(`āœ… Gmail connected for ${result.userId}. Refresh token saved.`);
336
+ } catch (err) {
337
+ console.error(`āŒ Gmail OAuth failed: ${err.message}`);
338
+ console.log('You can retry later with: mint gmail auth');
339
+ }
340
+ } else {
341
+ console.log('You can connect Gmail later with: mint gmail auth');
342
+ }
343
+ }
344
+
93
345
  // Install Daemon if requested
94
346
  if (options.installDaemon) {
95
347
  console.log('\nšŸš€ Installing Mint Background Agent (Daemon)...');