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.
- package/bin/obx +41 -154
- package/package.json +13 -28
- package/src/auth.js +181 -381
- package/src/commands/account.js +35 -88
- package/src/commands/data.js +40 -114
- package/src/commands/debug.js +28 -25
- package/src/commands/diagram.js +340 -0
- package/src/commands/earnings.js +31 -86
- package/src/commands/mission.js +194 -98
- package/src/commands/morning.js +39 -107
- package/src/commands/open.js +26 -36
- package/src/commands/publish.js +81 -126
- package/src/commands/research.js +148 -281
- package/src/commands/timeline.js +15 -54
- package/src/firebase.js +4 -14
- package/src/user.js +94 -0
- package/src/utils/display.js +27 -856
- package/src/api.js +0 -72
- package/src/config.js +0 -16
|
@@ -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 };
|
package/src/commands/earnings.js
CHANGED
|
@@ -1,121 +1,66 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const {
|
|
3
|
-
const {
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
7
|
+
async function earnings() {
|
|
8
|
+
if (!isAuthenticated()) { console.error(chalk.red('✗ Not authenticated')); process.exit(1); return; }
|
|
15
9
|
|
|
16
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
52
|
-
|
|
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(`
|
|
81
|
-
|
|
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(`
|
|
88
|
-
console.log(chalk.white(`
|
|
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 (
|
|
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
|
-
|
|
102
|
-
const
|
|
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 || '
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|