omnibiofex 2.8.5 → 4.1.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.
@@ -0,0 +1,340 @@
1
+ const chalk = require('chalk');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { getAuthToken, isAuthenticated } = require('../auth');
6
+ const { PremiumSpinner, sleep } = require('../utils/display');
7
+
8
+ const BACKEND_URL = 'https://obxvisionassistant-yyedhmslhq-uc.a.run.app';
9
+ const DIAGRAMS_DIR = path.join(os.homedir(), 'obx-diagrams');
10
+
11
+ // Ensure diagrams directory exists
12
+ if (!fs.existsSync(DIAGRAMS_DIR)) {
13
+ fs.mkdirSync(DIAGRAMS_DIR, { recursive: true });
14
+ }
15
+
16
+ // Diagram styles
17
+ const STYLES = {
18
+ minimal: {
19
+ name: 'Minimal',
20
+ cost: 40,
21
+ description: 'Clean, simple flowchart'
22
+ },
23
+ detailed: {
24
+ name: 'Detailed',
25
+ cost: 80,
26
+ description: 'Comprehensive ecosystem with all components'
27
+ },
28
+ flowchart: {
29
+ name: 'Flowchart',
30
+ cost: 60,
31
+ description: 'Step-by-step process flow'
32
+ },
33
+ network: {
34
+ name: 'Network',
35
+ cost: 70,
36
+ description: 'Interconnected nodes and relationships'
37
+ }
38
+ };
39
+
40
+ // Generate ASCII box
41
+ function box(text, width = 60) {
42
+ const padding = Math.max(0, width - text.length - 2);
43
+ const leftPad = Math.floor(padding / 2);
44
+ const rightPad = padding - leftPad;
45
+ return `║ ${' '.repeat(leftPad)}${text}${' '.repeat(rightPad)} ║`;
46
+ }
47
+
48
+ // Generate ASCII banner
49
+ function banner(title, width = 80) {
50
+ const top = `╔${'═'.repeat(width)}╗`;
51
+ const bottom = `╚${'═'.repeat(width)}╝`;
52
+ const content = box(title, width);
53
+ return `${top}\n${content}\n${bottom}`;
54
+ }
55
+
56
+ // Generate node box
57
+ function node(text, width = 20) {
58
+ const top = `┌${'─'.repeat(width)}┐`;
59
+ const bottom = `└${'─'.repeat(width)}┘`;
60
+ const padding = Math.max(0, width - text.length - 2);
61
+ const leftPad = Math.floor(padding / 2);
62
+ const rightPad = padding - leftPad;
63
+ const content = `│ ${' '.repeat(leftPad)}${text}${' '.repeat(rightPad)} │`;
64
+ return `${top}\n${content}\n${bottom}`;
65
+ }
66
+
67
+ // Generate arrow
68
+ function arrow(direction = 'down') {
69
+ switch (direction) {
70
+ case 'down': return '│\n▼';
71
+ case 'right': return '───▶';
72
+ case 'left': return '◀───';
73
+ case 'up': return '▲\n│';
74
+ default: return '│\n▼';
75
+ }
76
+ }
77
+
78
+ // Generate minimal diagram
79
+ function generateMinimalDiagram(topic, components) {
80
+ const width = 60;
81
+ let diagram = banner(topic.toUpperCase(), width) + '\n\n';
82
+
83
+ // Problem box
84
+ diagram += `${' '.repeat(20)}${node('Problem', 20)}\n`;
85
+ diagram += `${' '.repeat(29)}${arrow('down')}\n`;
86
+
87
+ // Three branches
88
+ diagram += `${' '.repeat(10)}${node('Research', 20)}\n`;
89
+ diagram += `${' '.repeat(19)}${arrow('down')}\n`;
90
+ diagram += `${' '.repeat(10)}${node('Analysis', 20)}\n`;
91
+ diagram += `${' '.repeat(19)}${arrow('down')}\n`;
92
+ diagram += `${' '.repeat(10)}${node('Solution', 20)}\n`;
93
+
94
+ return diagram;
95
+ }
96
+
97
+ // Generate detailed diagram
98
+ function generateDetailedDiagram(topic, components) {
99
+ const width = 80;
100
+ let diagram = banner(`${topic.toUpperCase()} RESEARCH ECOSYSTEM`, width) + '\n\n';
101
+
102
+ // Problem identification
103
+ diagram += `${' '.repeat(25)}${node('Research Problem', 25)}\n`;
104
+ diagram += `${' '.repeat(37)}${arrow('down')}\n\n`;
105
+
106
+ // Three input sources
107
+ diagram += `${' '.repeat(5)}${node('Literature', 18)} ${node('Data', 18)} ${node('Theory', 18)}\n`;
108
+ diagram += `${' '.repeat(14)}\\ | /\n`;
109
+ diagram += `${' '.repeat(15)}\\ | /\n`;
110
+ diagram += `${' '.repeat(16)}\\ | /\n`;
111
+ diagram += `${' '.repeat(17)}▼ ▼ ▼\n`;
112
+ diagram += `${' '.repeat(15)}${node('Integration Layer', 30)}\n`;
113
+ diagram += `${' '.repeat(29)}${arrow('down')}\n`;
114
+
115
+ // Analysis phase
116
+ diagram += `${' '.repeat(20)}${node('Analysis Phase', 25)}\n`;
117
+ diagram += `${' '.repeat(32)}${arrow('down')}\n\n`;
118
+
119
+ // Four analysis types
120
+ diagram += `${node('Statistical', 14)} ${node('Qualitative', 14)} ${node('Comparative', 14)} ${node('Predictive', 14)}\n`;
121
+ diagram += `${' '.repeat(7)}\\ | | /\n`;
122
+ diagram += `${' '.repeat(8)}\\ | | /\n`;
123
+ diagram += `${' '.repeat(9)}\\ | | /\n`;
124
+ diagram += `${' '.repeat(10)}▼ ▼ ▼ ▼\n`;
125
+ diagram += `${' '.repeat(15)}${node('Synthesis & Insights', 35)}\n`;
126
+ diagram += `${' '.repeat(32)}${arrow('down')}\n`;
127
+
128
+ // Output phase
129
+ diagram += `${' '.repeat(20)}${node('Output Phase', 25)}\n`;
130
+ diagram += `${' '.repeat(32)}${arrow('down')}\n\n`;
131
+
132
+ // Three outputs
133
+ diagram += `${node('Publication', 18)} ${node('Implementation', 18)} ${node('Future Work', 18)}\n`;
134
+
135
+ return diagram;
136
+ }
137
+
138
+ // Generate flowchart diagram
139
+ function generateFlowchartDiagram(topic, steps) {
140
+ const width = 60;
141
+ let diagram = banner(`${topic.toUpperCase()} WORKFLOW`, width) + '\n\n';
142
+
143
+ let currentStep = 1;
144
+ for (const step of steps) {
145
+ const stepText = `Step ${currentStep}: ${step}`;
146
+ diagram += `${' '.repeat(15)}${node(stepText, 45)}\n`;
147
+ if (currentStep < steps.length) {
148
+ diagram += `${' '.repeat(37)}${arrow('down')}\n`;
149
+ }
150
+ currentStep++;
151
+ }
152
+
153
+ diagram += `\n${' '.repeat(25)}${node('✓ Complete', 20)}\n`;
154
+
155
+ return diagram;
156
+ }
157
+
158
+ // Generate network diagram
159
+ function generateNetworkDiagram(topic, nodes) {
160
+ const width = 70;
161
+ let diagram = banner(`${topic.toUpperCase()} NETWORK`, width) + '\n\n';
162
+
163
+ // Central node
164
+ diagram += `${' '.repeat(25)}${node(topic, 25)}\n`;
165
+ diagram += `${' '.repeat(37)}|\n`;
166
+ diagram += `${' '.repeat(20)}┌────────┼────────┐\n`;
167
+ diagram += `${' '.repeat(20)}│ │ │\n`;
168
+ diagram += `${' '.repeat(20)}▼ ▼ ▼\n`;
169
+
170
+ // Three connected nodes
171
+ diagram += `${node(nodes[0] || 'Node A', 18)} ${node(nodes[1] || 'Node B', 18)} ${node(nodes[2] || 'Node C', 18)}\n`;
172
+ diagram += `${' '.repeat(9)}│ │ │\n`;
173
+ diagram += `${' '.repeat(9)}└────────┼────────┘\n`;
174
+ diagram += `${' '.repeat(18)}▼\n`;
175
+ diagram += `${' '.repeat(15)}${node('Integration', 25)}\n`;
176
+
177
+ return diagram;
178
+ }
179
+
180
+ // Generate Mermaid diagram for markdown
181
+ function generateMermaidDiagram(topic, components, style) {
182
+ let mermaid = '```mermaid\n';
183
+ mermaid += 'graph TD\n';
184
+ mermaid += ` A[${topic}] --> B[Research Phase]\n`;
185
+ mermaid += ` B --> C[Analysis Phase]\n`;
186
+ mermaid += ` C --> D[Output Phase]\n`;
187
+
188
+ if (components && components.length > 0) {
189
+ components.forEach((comp, i) => {
190
+ mermaid += ` B --> E${i}[${comp}]\n`;
191
+ });
192
+ }
193
+
194
+ mermaid += '```';
195
+ return mermaid;
196
+ }
197
+
198
+ // Main diagram command
199
+ async function diagram(topic, options = {}) {
200
+ if (!isAuthenticated()) {
201
+ console.error(chalk.red('✗ Not authenticated. Please run: obx login'));
202
+ process.exit(1);
203
+ return;
204
+ }
205
+
206
+ if (!topic) {
207
+ console.error(chalk.red('✗ Please provide a topic'));
208
+ console.log(chalk.gray('Usage: obx diagram "topic" [--style minimal|detailed|flowchart|network] [--save] [--publish]'));
209
+ process.exit(1);
210
+ return;
211
+ }
212
+
213
+ const style = options.style || 'detailed';
214
+ const save = options.save || false;
215
+ const publish = options.publish || false;
216
+ const cost = STYLES[style]?.cost || 80;
217
+
218
+ console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════'));
219
+ console.log(chalk.white.bold('📊 ASCII DIAGRAM GENERATOR'));
220
+ console.log(chalk.hex('#F24E1E')('═══════════════════════════════════════════════════════════\n'));
221
+
222
+ console.log(chalk.white(` 📌 Topic: ${chalk.hex('#F24E1E')(topic)}`));
223
+ console.log(chalk.white(` 🎨 Style: ${chalk.hex('#F24E1E')(STYLES[style]?.name || 'Detailed')}`));
224
+ console.log(chalk.white(` 💰 Cost: ${chalk.green(cost + ' RCC')}`));
225
+
226
+ const spinner = new PremiumSpinner('Generating diagram');
227
+ spinner.start();
228
+
229
+ try {
230
+ const token = await getAuthToken();
231
+
232
+ // Call AI backend to get components/structure
233
+ spinner.update('Analyzing topic with AI');
234
+ await sleep(500);
235
+
236
+ const response = await fetch(BACKEND_URL, {
237
+ method: 'POST',
238
+ headers: {
239
+ 'Content-Type': 'application/json',
240
+ 'Authorization': `Bearer ${token}`
241
+ },
242
+ body: JSON.stringify({
243
+ taskType: 'RESEARCH_DIAGRAM',
244
+ message: `Generate a structured breakdown of "${topic}" for an ASCII diagram. Return a JSON object with:
245
+ {
246
+ "components": ["component1", "component2", ...],
247
+ "steps": ["step1", "step2", ...],
248
+ "nodes": ["node1", "node2", "node3"],
249
+ "description": "brief description"
250
+ }
251
+
252
+ Only return valid JSON, no other text.`,
253
+ model: 'deep'
254
+ })
255
+ });
256
+
257
+ if (!response.ok) {
258
+ throw new Error(`Backend returned ${response.status}`);
259
+ }
260
+
261
+ spinner.update('Rendering ASCII art');
262
+ await sleep(500);
263
+
264
+ const data = await response.json();
265
+
266
+ // Parse AI response
267
+ let aiData = { components: [], steps: [], nodes: [] };
268
+ try {
269
+ // Try to extract JSON from response
270
+ const jsonMatch = data.response.match(/\{[\s\S]*\}/);
271
+ if (jsonMatch) {
272
+ aiData = JSON.parse(jsonMatch[0]);
273
+ }
274
+ } catch (e) {
275
+ console.log(chalk.yellow(' ⚠ Using default structure'));
276
+ }
277
+
278
+ // Generate diagram based on style
279
+ let asciiDiagram;
280
+ switch (style) {
281
+ case 'minimal':
282
+ asciiDiagram = generateMinimalDiagram(topic, aiData.components);
283
+ break;
284
+ case 'flowchart':
285
+ asciiDiagram = generateFlowchartDiagram(topic, aiData.steps.length > 0 ? aiData.steps : ['Research', 'Analysis', 'Implementation']);
286
+ break;
287
+ case 'network':
288
+ asciiDiagram = generateNetworkDiagram(topic, aiData.nodes);
289
+ break;
290
+ case 'detailed':
291
+ default:
292
+ asciiDiagram = generateDetailedDiagram(topic, aiData.components);
293
+ break;
294
+ }
295
+
296
+ // Generate Mermaid version
297
+ const mermaidDiagram = generateMermaidDiagram(topic, aiData.components, style);
298
+
299
+ spinner.succeed('✓ Diagram generated');
300
+
301
+ // Display diagram
302
+ console.log('\n' + chalk.cyan(asciiDiagram) + '\n');
303
+
304
+ // Save if requested
305
+ if (save || publish) {
306
+ const timestamp = Date.now();
307
+ const safeTopic = topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 50);
308
+
309
+ // Save ASCII version
310
+ const txtFilename = `${safeTopic}-${timestamp}.txt`;
311
+ const txtPath = path.join(DIAGRAMS_DIR, txtFilename);
312
+ fs.writeFileSync(txtPath, asciiDiagram, 'utf8');
313
+ console.log(chalk.green(` 💾 Saved ASCII: ${txtPath}`));
314
+
315
+ // Save Markdown version with Mermaid
316
+ const mdFilename = `${safeTopic}-${timestamp}.md`;
317
+ const mdPath = path.join(DIAGRAMS_DIR, mdFilename);
318
+ const mdContent = `# ${topic} Research Diagram\n\n**Style:** ${STYLES[style].name}\n**Generated:** ${new Date().toLocaleString()}\n\n## ASCII Diagram\n\n\`\`\`\n${asciiDiagram}\n\`\`\`\n\n## Mermaid Version\n\n${mermaidDiagram}\n`;
319
+ fs.writeFileSync(mdPath, mdContent, 'utf8');
320
+ console.log(chalk.green(` 💾 Saved Markdown: ${mdPath}`));
321
+ }
322
+
323
+ // Publish if requested
324
+ if (publish) {
325
+ console.log(chalk.yellow('\n 📤 To publish this diagram as research:'));
326
+ console.log(chalk.gray(' 1. Review the saved markdown file'));
327
+ console.log(chalk.gray(' 2. Run: obx publish'));
328
+ console.log(chalk.gray(' 3. Select the diagram from your missions'));
329
+ }
330
+
331
+ console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════\n'));
332
+
333
+ } catch (error) {
334
+ spinner.fail('Failed to generate diagram');
335
+ console.error(chalk.red(error.message));
336
+ process.exit(1);
337
+ }
338
+ }
339
+
340
+ module.exports = { diagram, STYLES };
@@ -1,121 +1,66 @@
1
1
  const chalk = require('chalk');
2
- const { isAuthenticated, getCurrentUserUid, getCurrentUserEmail } = require('../auth');
3
- const { db } = require('../firebase');
4
- const { PremiumSpinner, sleep } = require('../utils/display');
5
- const { collection, query, where, orderBy, limit, getDocs } = require('firebase/firestore');
2
+ const { getAuthToken, isAuthenticated } = require('../auth');
3
+ const { PremiumSpinner } = require('../utils/display');
6
4
 
7
- async function earnings() {
8
- if (!isAuthenticated()) {
9
- console.error(chalk.red('✗ Not authenticated. Please run: obx login'));
10
- return;
11
- }
5
+ const GET_USER_DATA_URL = 'https://getuserdata-yyedhmslhq-uc.a.run.app';
12
6
 
13
- const uid = getCurrentUserUid();
14
- const email = getCurrentUserEmail();
7
+ async function earnings() {
8
+ if (!isAuthenticated()) { console.error(chalk.red('✗ Not authenticated')); process.exit(1); return; }
15
9
 
16
- if (!uid) {
17
- console.error(chalk.red('✗ Could not extract user ID from token.'));
18
- console.error(chalk.gray('Please run: obx login'));
19
- return;
20
- }
21
-
22
- const spinner = new PremiumSpinner('Fetching your earnings data');
10
+ const spinner = new PremiumSpinner('Fetching earnings data');
23
11
  spinner.start();
24
12
 
25
13
  try {
26
- const publishedQuery = query(
27
- collection(db, 'missions'),
28
- where('uid', '==', uid),
29
- where('isPublished', '==', true),
30
- orderBy('publishedAt', 'desc'),
31
- limit(100)
32
- );
33
-
34
- const querySnapshot = await getDocs(publishedQuery);
35
- const publishedMissions = [];
36
-
37
- querySnapshot.forEach((docSnap) => {
38
- publishedMissions.push({
39
- id: docSnap.id,
40
- ...docSnap.data()
41
- });
14
+ const token = await getAuthToken();
15
+ const response = await fetch(GET_USER_DATA_URL, {
16
+ headers: { 'Authorization': `Bearer ${token}` }
42
17
  });
43
-
18
+ if (!response.ok) throw new Error(`Backend error: ${response.status}`);
19
+ const userData = await response.json();
20
+
44
21
  spinner.succeed('Earnings data loaded');
45
-
46
- const totalViews = publishedMissions.reduce((sum, m) => sum + (m.views || 0), 0);
47
- const totalDownloads = publishedMissions.reduce((sum, m) => sum + (m.downloads || 0), 0);
48
- const totalCitations = publishedMissions.reduce((sum, m) => sum + (m.citations || 0), 0);
49
- const totalEarnings = publishedMissions.reduce((sum, m) => sum + (m.earnings || 0), 0);
50
22
 
51
- const now = new Date();
52
- const thisMonth = now.getMonth();
53
- const thisYear = now.getFullYear();
54
- const lastMonth = thisMonth === 0 ? 11 : thisMonth - 1;
55
- const lastMonthYear = thisMonth === 0 ? thisYear - 1 : thisYear;
56
-
57
- const thisMonthMissions = publishedMissions.filter(m => {
58
- const publishedDate = m.publishedAt?.toDate?.();
59
- return publishedDate && publishedDate.getMonth() === thisMonth && publishedDate.getFullYear() === thisYear;
60
- });
61
-
62
- const lastMonthMissions = publishedMissions.filter(m => {
63
- const publishedDate = m.publishedAt?.toDate?.();
64
- return publishedDate && publishedDate.getMonth() === lastMonth && publishedDate.getFullYear() === lastMonthYear;
65
- });
66
-
67
- const thisMonthEarnings = thisMonthMissions.reduce((sum, m) => sum + (m.earnings || 0), 0);
68
- const thisMonthViews = thisMonthMissions.reduce((sum, m) => sum + (m.views || 0), 0);
69
- const lastMonthEarnings = lastMonthMissions.reduce((sum, m) => sum + (m.earnings || 0), 0);
70
-
23
+ const published = userData.missions.filter(m => m.isPublished === true);
24
+
71
25
  console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════'));
72
26
  console.log(chalk.white.bold('💰 EARNINGS DASHBOARD'));
73
27
  console.log(chalk.hex('#F24E1E')('═══════════════════════════════════════════════════════════\n'));
28
+ console.log(chalk.gray(` 👤 User: ${chalk.white(userData.user.name || userData.user.email)}`));
74
29
 
75
- console.log(chalk.gray(` 👤 User: ${chalk.white(email || 'Unknown')}`));
76
-
77
30
  console.log(chalk.white.bold('\n\n📊 OVERVIEW'));
78
31
  console.log(chalk.gray('─'.repeat(60)));
79
- console.log(chalk.white(`\n 💰 Total Earnings: ${chalk.green('₹' + totalEarnings.toLocaleString())}`));
80
- console.log(chalk.white(` 📅 This Month: ${chalk.green('₹' + thisMonthEarnings.toLocaleString())}`));
81
- console.log(chalk.white(` 📅 Last Month: ${chalk.gray('₹' + lastMonthEarnings.toLocaleString())}`));
82
- console.log(chalk.white(` 📄 Published Research: ${chalk.green(publishedMissions.length)}`));
83
-
32
+ console.log(chalk.white(`\n 💰 Total Earnings: ${chalk.green('₹' + userData.stats.totalEarnings.toLocaleString())}`));
33
+ console.log(chalk.white(` 📄 Published Research: ${chalk.green(published.length)}`));
34
+
84
35
  console.log(chalk.white.bold('\n\n📈 TRAFFIC METRICS'));
85
36
  console.log(chalk.gray('─'.repeat(60)));
86
- console.log(chalk.white(`\n 👁 Total Views: ${chalk.green(totalViews.toLocaleString())}`));
87
- console.log(chalk.white(` 👁 This Month: ${chalk.green(thisMonthViews.toLocaleString())}`));
88
- console.log(chalk.white(` 💾 Total Downloads: ${chalk.green(totalDownloads.toLocaleString())}`));
89
- console.log(chalk.white(` 📚 Total Citations: ${chalk.green(totalCitations.toLocaleString())}`));
37
+ console.log(chalk.white(`\n 👁 Total Views: ${chalk.green(userData.stats.totalViews.toLocaleString())}`));
38
+ console.log(chalk.white(` 💾 Total Downloads: ${chalk.green(userData.stats.totalDownloads.toLocaleString())}`));
39
+ console.log(chalk.white(` 📚 Total Citations: ${chalk.green(userData.stats.totalCitations.toLocaleString())}`));
90
40
 
91
- if (totalViews > 0) {
92
- const averagePerView = totalEarnings / totalViews;
93
- console.log(chalk.white(` 💵 Average Per View: ${chalk.green('₹' + averagePerView.toFixed(3))}`));
94
- }
95
-
96
- if (publishedMissions.length > 0) {
41
+ if (published.length > 0) {
97
42
  console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════'));
98
43
  console.log(chalk.white.bold('📊 YOUR PUBLISHED RESEARCH'));
99
44
  console.log(chalk.hex('#F24E1E')('═══════════════════════════════════════════════════════════\n'));
100
-
101
- publishedMissions.slice(0, 10).forEach((m, i) => {
102
- const publishedDate = m.publishedAt?.toDate?.().toLocaleDateString() || 'N/A';
45
+
46
+ published.slice(0, 10).forEach((m, i) => {
47
+ const date = m.publishedAt ? new Date(m.publishedAt).toLocaleDateString() : 'N/A';
103
48
  console.log(chalk.white(`\n ${i + 1}. ${chalk.hex('#F24E1E')(m.id)}`));
104
- console.log(chalk.white(` Topic: ${(m.message || 'No topic').substring(0, 50)}${(m.message?.length || 0) > 50 ? '...' : ''}`));
49
+ console.log(chalk.white(` Topic: ${(m.message || '').substring(0, 50)}`));
105
50
  console.log(chalk.white(` 👁 Views: ${chalk.green((m.views || 0).toLocaleString())}`));
106
51
  console.log(chalk.white(` 💰 Earnings: ${chalk.green('₹' + (m.earnings || 0).toLocaleString())}`));
107
- console.log(chalk.gray(` 📅 Published: ${publishedDate}`));
52
+ console.log(chalk.gray(` 📅 Published: ${date}`));
108
53
  });
109
54
  } else {
110
55
  console.log(chalk.yellow('\n\n ⚠️ No published research yet.'));
111
- console.log(chalk.gray(' Run "obx publish" to publish your first research and start earning.\n'));
56
+ console.log(chalk.gray(' Run "obx publish" to publish your first research.\n'));
112
57
  }
113
-
114
58
  console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════\n'));
115
-
59
+ process.exit(0);
116
60
  } catch (error) {
117
- spinner.fail('Failed to fetch earnings data');
61
+ spinner.fail('Failed to fetch earnings');
118
62
  console.error(chalk.red(error.message));
63
+ process.exit(1);
119
64
  }
120
65
  }
121
66