@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,208 @@
1
+ const { execFile } = require('child_process');
2
+ const pkg = require('../../package.json');
3
+
4
+ const NPM_COMMAND = process.platform === 'win32' ? 'npm.cmd' : 'npm';
5
+ const DEFAULT_AUTO_UPDATE_INTERVAL_HOURS = 24;
6
+
7
+ function execFilePromise(command, args, options = {}) {
8
+ return new Promise((resolve, reject) => {
9
+ execFile(command, args, options, (error, stdout, stderr) => {
10
+ if (error) {
11
+ error.stdout = stdout;
12
+ error.stderr = stderr;
13
+ reject(error);
14
+ return;
15
+ }
16
+ resolve({ stdout, stderr });
17
+ });
18
+ });
19
+ }
20
+
21
+ function parseVersion(version) {
22
+ return String(version || '')
23
+ .trim()
24
+ .replace(/^v/, '')
25
+ .split('-')[0]
26
+ .split('.')
27
+ .map((part) => Number.parseInt(part, 10) || 0);
28
+ }
29
+
30
+ function compareVersions(a, b) {
31
+ const left = parseVersion(a);
32
+ const right = parseVersion(b);
33
+ const length = Math.max(left.length, right.length, 3);
34
+
35
+ for (let i = 0; i < length; i++) {
36
+ const l = left[i] || 0;
37
+ const r = right[i] || 0;
38
+ if (l > r) return 1;
39
+ if (l < r) return -1;
40
+ }
41
+ return 0;
42
+ }
43
+
44
+ function normalizeNpmVersionOutput(output) {
45
+ const trimmed = String(output || '').trim();
46
+ if (!trimmed) return '';
47
+
48
+ try {
49
+ const parsed = JSON.parse(trimmed);
50
+ if (typeof parsed === 'string') return parsed;
51
+ } catch (err) {
52
+ // npm may return plain text depending on config/version.
53
+ }
54
+
55
+ return trimmed.replace(/^['"]|['"]$/g, '');
56
+ }
57
+
58
+ function getAutoUpdateIntervalMs(config = {}) {
59
+ const hours = Number(config.autoUpdateCheckIntervalHours);
60
+ const safeHours = Number.isFinite(hours) && hours > 0
61
+ ? hours
62
+ : DEFAULT_AUTO_UPDATE_INTERVAL_HOURS;
63
+ return safeHours * 60 * 60 * 1000;
64
+ }
65
+
66
+ function shouldRunAutoUpdate(config = {}, now = Date.now()) {
67
+ if (config.enableAutoUpdate === false) return false;
68
+
69
+ const lastCheck = Date.parse(config.lastUpdateCheckAt || '');
70
+ if (!Number.isFinite(lastCheck)) return true;
71
+
72
+ return now - lastCheck >= getAutoUpdateIntervalMs(config);
73
+ }
74
+
75
+ async function getLatestVersion(packageName = pkg.name) {
76
+ const { stdout } = await execFilePromise(NPM_COMMAND, ['view', packageName, 'version', '--json'], {
77
+ maxBuffer: 1024 * 1024
78
+ });
79
+ return normalizeNpmVersionOutput(stdout);
80
+ }
81
+
82
+ async function installLatest(packageName = pkg.name, options = {}) {
83
+ const args = ['install', '-g', `${packageName}@latest`];
84
+ if (options.dryRun) {
85
+ args.push('--dry-run');
86
+ }
87
+
88
+ return await execFilePromise(NPM_COMMAND, args, {
89
+ maxBuffer: 1024 * 1024 * 8
90
+ });
91
+ }
92
+
93
+ function formatUpdateError(error) {
94
+ const detail = [error.stderr, error.stdout, error.message].filter(Boolean).join('\n').trim();
95
+ if (/EACCES|permission denied|Access is denied/i.test(detail)) {
96
+ return [
97
+ 'Update failed because npm does not have permission to modify the global install directory.',
98
+ `Run manually: npm install -g ${pkg.name}@latest`,
99
+ 'If your npm global packages require sudo, run that command with sudo.'
100
+ ].join('\n');
101
+ }
102
+
103
+ if (/E404|404 Not Found|not in this registry/i.test(detail)) {
104
+ return [
105
+ `Could not find ${pkg.name} on the npm registry.`,
106
+ 'Publish the package first, or update Mint from the source/release channel you installed from.'
107
+ ].join('\n');
108
+ }
109
+
110
+ return `Update failed: ${detail || 'Unknown npm error'}`;
111
+ }
112
+
113
+ async function runUpdate(options = {}) {
114
+ const currentVersion = pkg.version;
115
+ let latestVersion = '';
116
+
117
+ try {
118
+ latestVersion = await getLatestVersion(pkg.name);
119
+ } catch (error) {
120
+ return {
121
+ status: 'error',
122
+ currentVersion,
123
+ latestVersion,
124
+ message: formatUpdateError(error)
125
+ };
126
+ }
127
+
128
+ if (!latestVersion) {
129
+ return {
130
+ status: 'error',
131
+ currentVersion,
132
+ latestVersion: '',
133
+ message: 'Could not determine the latest Mint version from npm.'
134
+ };
135
+ }
136
+
137
+ const comparison = compareVersions(currentVersion, latestVersion);
138
+ if (comparison >= 0) {
139
+ return {
140
+ status: 'current',
141
+ currentVersion,
142
+ latestVersion,
143
+ message: `Mint is already up to date (${currentVersion}).`
144
+ };
145
+ }
146
+
147
+ if (options.checkOnly) {
148
+ return {
149
+ status: 'available',
150
+ currentVersion,
151
+ latestVersion,
152
+ message: `Mint ${latestVersion} is available. Current version: ${currentVersion}.`
153
+ };
154
+ }
155
+
156
+ try {
157
+ await installLatest(pkg.name, { dryRun: options.dryRun });
158
+ return {
159
+ status: options.dryRun ? 'dry-run' : 'updated',
160
+ currentVersion,
161
+ latestVersion,
162
+ message: options.dryRun
163
+ ? `Dry run complete. Mint would update from ${currentVersion} to ${latestVersion}.`
164
+ : `Mint updated from ${currentVersion} to ${latestVersion}. Restart mint to use the new version.`
165
+ };
166
+ } catch (error) {
167
+ return {
168
+ status: 'error',
169
+ currentVersion,
170
+ latestVersion,
171
+ message: formatUpdateError(error)
172
+ };
173
+ }
174
+ }
175
+
176
+ async function runStartupAutoUpdate(config, writeConfig, options = {}) {
177
+ const now = options.now || Date.now();
178
+ if (!shouldRunAutoUpdate(config, now)) {
179
+ return {
180
+ status: 'skipped',
181
+ message: 'Auto-update check skipped by cooldown.'
182
+ };
183
+ }
184
+
185
+ if (typeof writeConfig === 'function') {
186
+ writeConfig({
187
+ ...config,
188
+ lastUpdateCheckAt: new Date(now).toISOString()
189
+ });
190
+ }
191
+
192
+ return await runUpdate({ checkOnly: false });
193
+ }
194
+
195
+ module.exports = {
196
+ compareVersions,
197
+ getLatestVersion,
198
+ installLatest,
199
+ normalizeNpmVersionOutput,
200
+ runUpdate,
201
+ runStartupAutoUpdate,
202
+ shouldRunAutoUpdate,
203
+ _private: {
204
+ parseVersion,
205
+ formatUpdateError,
206
+ getAutoUpdateIntervalMs
207
+ }
208
+ };
@@ -0,0 +1,35 @@
1
+ const axios = require('axios');
2
+
3
+ class BraveSearchBridge {
4
+ constructor(credentials) {
5
+ this.apiKey = credentials.apiKey;
6
+ }
7
+
8
+ async search(query) {
9
+ if (!this.apiKey) {
10
+ throw new Error('Brave Search API Key is required.');
11
+ }
12
+
13
+ try {
14
+ const response = await axios.get('https://api.search.brave.com/res/v1/web/search', {
15
+ params: { q: query, count: 5 },
16
+ headers: {
17
+ 'Accept': 'application/json',
18
+ 'Accept-Encoding': 'gzip',
19
+ 'X-Subscription-Token': this.apiKey
20
+ }
21
+ });
22
+
23
+ const results = response.data.web ? response.data.web.results : [];
24
+ return results.map(item => ({
25
+ title: item.title,
26
+ snippet: item.description,
27
+ link: item.url
28
+ }));
29
+ } catch (err) {
30
+ throw new Error(`Brave Search Failed: ${err.message}`);
31
+ }
32
+ }
33
+ }
34
+
35
+ module.exports = BraveSearchBridge;
@@ -0,0 +1,68 @@
1
+ const { Client, GatewayIntentBits, Partials } = require('discord.js');
2
+ const { handleChat } = require('../AI_Brain/Gemini_API');
3
+
4
+ class DiscordBridge {
5
+ constructor(token) {
6
+ this.token = token;
7
+ this.client = new Client({
8
+ intents: [
9
+ GatewayIntentBits.Guilds,
10
+ GatewayIntentBits.GuildMessages,
11
+ GatewayIntentBits.MessageContent,
12
+ GatewayIntentBits.DirectMessages
13
+ ],
14
+ partials: [Partials.Channel]
15
+ });
16
+ }
17
+
18
+ async connect() {
19
+ this.client.on('ready', () => {
20
+ console.log(`[Discord Bridge] Logged in as ${this.client.user.tag}!`);
21
+ });
22
+
23
+ this.client.on('messageCreate', async (message) => {
24
+ // Ignore bot messages
25
+ if (message.author.bot) return;
26
+
27
+ // Handle DMs or Mentions
28
+ const isDM = !message.guild;
29
+ const isMentioned = message.mentions.has(this.client.user);
30
+
31
+ if (isDM || isMentioned) {
32
+ try {
33
+ // Clean up the message if it's a mention
34
+ let cleanContent = message.content;
35
+ if (isMentioned) {
36
+ cleanContent = message.content.replace(`<@!${this.client.user.id}>`, '').replace(`<@${this.client.user.id}>`, '').trim();
37
+ }
38
+
39
+ if (!cleanContent) return;
40
+
41
+ // Show typing indicator
42
+ await message.channel.sendTyping();
43
+
44
+ // Send to Mint AI Brain
45
+ const result = await handleChat(cleanContent);
46
+
47
+ // Reply to user
48
+ if (result && result.response) {
49
+ await message.reply(result.response);
50
+ }
51
+ } catch (err) {
52
+ console.error('[Discord Bridge] Error processing message:', err);
53
+ await message.reply('ขออภัยค่ะ เกิดข้อผิดพลาดบางอย่างในการประมวลผลข้อความ');
54
+ }
55
+ }
56
+ });
57
+
58
+ await this.client.login(this.token);
59
+ }
60
+
61
+ async disconnect() {
62
+ if (this.client) {
63
+ await this.client.destroy();
64
+ }
65
+ }
66
+ }
67
+
68
+ module.exports = DiscordBridge;
@@ -0,0 +1,38 @@
1
+ const axios = require('axios');
2
+
3
+ class GoogleSearchBridge {
4
+ constructor(credentials) {
5
+ this.apiKey = credentials.apiKey;
6
+ this.cx = credentials.cx; // Custom Search Engine ID
7
+ }
8
+
9
+ async search(query) {
10
+ if (!this.apiKey || !this.cx) {
11
+ throw new Error('Google Search API Key and CX are required.');
12
+ }
13
+
14
+ try {
15
+ const response = await axios.get('https://www.googleapis.com/customsearch/v1', {
16
+ params: {
17
+ key: this.apiKey,
18
+ cx: this.cx,
19
+ q: query,
20
+ num: 5
21
+ }
22
+ });
23
+
24
+ const items = response.data.items || [];
25
+ return items.map(item => ({
26
+ title: item.title,
27
+ snippet: item.snippet,
28
+ link: item.link
29
+ }));
30
+ } catch (err) {
31
+ throw new Error(err.response && err.response.data && err.response.data.error
32
+ ? `Google Search API Error: ${err.response.data.error.message}`
33
+ : `Google Search Failed: ${err.message}`);
34
+ }
35
+ }
36
+ }
37
+
38
+ module.exports = GoogleSearchBridge;
@@ -0,0 +1,60 @@
1
+ const line = require('@line/bot-sdk');
2
+ const express = require('express');
3
+ const { handleChat } = require('../AI_Brain/Gemini_API');
4
+
5
+ class LineBridge {
6
+ constructor(credentials) {
7
+ this.config = {
8
+ channelAccessToken: credentials.accessToken,
9
+ channelSecret: credentials.secret,
10
+ };
11
+ this.port = credentials.port || 3000;
12
+ this.client = new line.messagingApi.MessagingApiClient({
13
+ channelAccessToken: credentials.accessToken
14
+ });
15
+ this.app = express();
16
+ }
17
+
18
+ async connect() {
19
+ this.app.post('/callback', line.middleware(this.config), (req, res) => {
20
+ Promise
21
+ .all(req.body.events.map(event => this.handleEvent(event)))
22
+ .then((result) => res.json(result))
23
+ .catch((err) => {
24
+ console.error('[LINE Bridge] Error:', err);
25
+ res.status(500).end();
26
+ });
27
+ });
28
+
29
+ this.server = this.app.listen(this.port, () => {
30
+ console.log(`[LINE Bridge] Listening for webhooks on port ${this.port}`);
31
+ console.log(`[LINE Bridge] Webhook URL should be: <YOUR_PUBLIC_URL>/callback`);
32
+ });
33
+ }
34
+
35
+ async handleEvent(event) {
36
+ if (event.type !== 'message' || event.message.type !== 'text') {
37
+ return Promise.resolve(null);
38
+ }
39
+
40
+ try {
41
+ const result = await handleChat(event.message.text);
42
+ if (result && result.response) {
43
+ return this.client.replyMessage({
44
+ replyToken: event.replyToken,
45
+ messages: [{ type: 'text', text: result.response }],
46
+ });
47
+ }
48
+ } catch (err) {
49
+ console.error('[LINE Bridge] Error processing event:', err);
50
+ }
51
+ }
52
+
53
+ async disconnect() {
54
+ if (this.server) {
55
+ this.server.close();
56
+ }
57
+ }
58
+ }
59
+
60
+ module.exports = LineBridge;
@@ -0,0 +1,53 @@
1
+ const { App } = require('@slack/bolt');
2
+ const { handleChat } = require('../AI_Brain/Gemini_API');
3
+
4
+ class SlackBridge {
5
+ constructor(credentials) {
6
+ this.app = new App({
7
+ token: credentials.botToken,
8
+ appToken: credentials.appToken,
9
+ socketMode: true
10
+ });
11
+ }
12
+
13
+ async connect() {
14
+ this.app.event('app_mention', async ({ event, say }) => {
15
+ try {
16
+ const text = event.text.replace(/<@.*?>/g, '').trim();
17
+ if (!text) return;
18
+
19
+ const result = await handleChat(text);
20
+ if (result && result.response) {
21
+ await say(result.response);
22
+ }
23
+ } catch (err) {
24
+ console.error('[Slack Bridge] Error processing app_mention:', err);
25
+ }
26
+ });
27
+
28
+ this.app.event('message', async ({ event, say }) => {
29
+ // Only respond in DMs
30
+ if (event.channel_type === 'im') {
31
+ try {
32
+ const result = await handleChat(event.text);
33
+ if (result && result.response) {
34
+ await say(result.response);
35
+ }
36
+ } catch (err) {
37
+ console.error('[Slack Bridge] Error processing message:', err);
38
+ }
39
+ }
40
+ });
41
+
42
+ await this.app.start();
43
+ console.log('[Slack Bridge] App started in Socket Mode!');
44
+ }
45
+
46
+ async disconnect() {
47
+ if (this.app) {
48
+ await this.app.stop();
49
+ }
50
+ }
51
+ }
52
+
53
+ module.exports = SlackBridge;
@@ -0,0 +1,49 @@
1
+ const { Telegraf } = require('telegraf');
2
+ const { handleChat } = require('../AI_Brain/Gemini_API');
3
+
4
+ class TelegramBridge {
5
+ constructor(token) {
6
+ this.token = token;
7
+ this.bot = new Telegraf(token);
8
+ }
9
+
10
+ async connect() {
11
+ this.bot.start((ctx) => ctx.reply('สวัสดีค่ะ! มิ้นท์พร้อมช่วยเหลือคุณใน Telegram แล้วนะคะ ✨'));
12
+
13
+ this.bot.on('text', async (ctx) => {
14
+ try {
15
+ // Show typing status
16
+ await ctx.sendChatAction('typing');
17
+
18
+ const message = ctx.message.text;
19
+ if (!message) return;
20
+
21
+ // Send to Mint AI Brain
22
+ const result = await handleChat(message);
23
+
24
+ // Reply to user
25
+ if (result && result.response) {
26
+ await ctx.reply(result.response);
27
+ }
28
+ } catch (err) {
29
+ console.error('[Telegram Bridge] Error processing message:', err);
30
+ await ctx.reply('ขออภัยค่ะ เกิดข้อผิดพลาดบางอย่างในการประมวลผลข้อความ');
31
+ }
32
+ });
33
+
34
+ this.bot.launch();
35
+ console.log('[Telegram Bridge] Bot started!');
36
+
37
+ // Enable graceful stop
38
+ process.once('SIGINT', () => this.bot.stop('SIGINT'));
39
+ process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
40
+ }
41
+
42
+ async disconnect() {
43
+ if (this.bot) {
44
+ await this.bot.stop();
45
+ }
46
+ }
47
+ }
48
+
49
+ module.exports = TelegramBridge;
@@ -0,0 +1,55 @@
1
+ const { Client, LocalAuth } = require('whatsapp-web.js');
2
+ const qrcode = require('qrcode-terminal');
3
+ const { handleChat } = require('../AI_Brain/Gemini_API');
4
+
5
+ class WhatsappBridge {
6
+ constructor() {
7
+ this.client = new Client({
8
+ authStrategy: new LocalAuth({
9
+ dataPath: require('path').join(require('os').homedir(), '.config', 'mint', 'whatsapp-session')
10
+ }),
11
+ puppeteer: {
12
+ args: ['--no-sandbox']
13
+ }
14
+ });
15
+ }
16
+
17
+ async connect() {
18
+ this.client.on('qr', (qr) => {
19
+ console.log('[WhatsApp Bridge] Scan this QR code to login:');
20
+ qrcode.generate(qr, { small: true });
21
+ });
22
+
23
+ this.client.on('ready', () => {
24
+ console.log('[WhatsApp Bridge] Client is ready!');
25
+ });
26
+
27
+ this.client.on('message', async (msg) => {
28
+ try {
29
+ // Ignore messages from groups unless mentioned (simple implementation)
30
+ const chat = await msg.getChat();
31
+ if (chat.isGroup) {
32
+ // For groups, we could add a mention check here if desired
33
+ return;
34
+ }
35
+
36
+ const result = await handleChat(msg.body);
37
+ if (result && result.response) {
38
+ await msg.reply(result.response);
39
+ }
40
+ } catch (err) {
41
+ console.error('[WhatsApp Bridge] Error processing message:', err);
42
+ }
43
+ });
44
+
45
+ await this.client.initialize();
46
+ }
47
+
48
+ async disconnect() {
49
+ if (this.client) {
50
+ await this.client.destroy();
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = WhatsappBridge;
@@ -1,6 +1,8 @@
1
1
  function parseCommand(aiResponse) {
2
2
  let action = { type: 'none', target: '' };
3
3
  let responseText = '';
4
+ let timestamp = null;
5
+ let providerInfo = null;
4
6
 
5
7
  if (typeof aiResponse === 'string') {
6
8
  // Attempt to parse string to JSON
@@ -8,6 +10,8 @@ function parseCommand(aiResponse) {
8
10
  const parsed = JSON.parse(aiResponse);
9
11
  action = parsed.action || action;
10
12
  responseText = parsed.response || '';
13
+ timestamp = parsed.timestamp || null;
14
+ providerInfo = parsed.providerInfo || null;
11
15
  } catch (e) {
12
16
  // Fallback for markdown
13
17
  const jsonMatch = aiResponse.match(/```json\n([\s\S]*?)\n```/) || aiResponse.match(/\{[\s\S]*\}/);
@@ -16,6 +20,8 @@ function parseCommand(aiResponse) {
16
20
  const parsed = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
17
21
  action = parsed.action || action;
18
22
  responseText = parsed.response || '';
23
+ timestamp = parsed.timestamp || null;
24
+ providerInfo = parsed.providerInfo || null;
19
25
  } catch (err) {
20
26
  responseText = aiResponse;
21
27
  }
@@ -26,9 +32,14 @@ function parseCommand(aiResponse) {
26
32
  } else if (typeof aiResponse === 'object') {
27
33
  action = aiResponse.action || action;
28
34
  responseText = aiResponse.response || '';
35
+ timestamp = aiResponse.timestamp || null;
36
+ providerInfo = aiResponse.providerInfo || null;
29
37
  }
30
38
 
31
- return { response: responseText, action };
39
+ const parsedResponse = { response: responseText, action };
40
+ if (timestamp) parsedResponse.timestamp = timestamp;
41
+ if (providerInfo) parsedResponse.providerInfo = providerInfo;
42
+ return parsedResponse;
32
43
  }
33
44
 
34
45
  module.exports = { parseCommand };