omnibiofex 2.8.2 โ 2.8.4
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 +20 -8
- package/package.json +1 -1
- package/src/commands/account.js +17 -21
- package/src/commands/data.js +109 -209
- package/src/commands/earnings.js +18 -29
- package/src/commands/morning.js +130 -73
- package/src/commands/publish.js +17 -31
- package/src/commands/timeline.js +64 -125
- package/src/firebase.js +108 -1
package/bin/obx
CHANGED
|
@@ -105,8 +105,17 @@ program
|
|
|
105
105
|
.argument('<topic>', 'Research topic')
|
|
106
106
|
.action(hypothesis);
|
|
107
107
|
|
|
108
|
+
// ==================== TIMELINE COMMAND ====================
|
|
109
|
+
const { timeline } = require('../src/commands/timeline');
|
|
110
|
+
|
|
111
|
+
program
|
|
112
|
+
.command('timeline')
|
|
113
|
+
.description('๐
Generate visual research timeline')
|
|
114
|
+
.argument('<topic>', 'Research topic')
|
|
115
|
+
.action(timeline);
|
|
116
|
+
|
|
108
117
|
// ==================== DATA COMMANDS ====================
|
|
109
|
-
const { dataset, code
|
|
118
|
+
const { dataset, code } = require('../src/commands/data');
|
|
110
119
|
|
|
111
120
|
program
|
|
112
121
|
.command('dataset')
|
|
@@ -120,12 +129,6 @@ program
|
|
|
120
129
|
.argument('[directory]', 'Directory path', '.')
|
|
121
130
|
.action(code);
|
|
122
131
|
|
|
123
|
-
program
|
|
124
|
-
.command('medical')
|
|
125
|
-
.description('Analyze medical imaging')
|
|
126
|
-
.argument('<file>', 'DICOM/image file path')
|
|
127
|
-
.action(medical);
|
|
128
|
-
|
|
129
132
|
// ==================== PUBLISH & EARN COMMANDS ====================
|
|
130
133
|
const { publish } = require('../src/commands/publish');
|
|
131
134
|
const { earnings } = require('../src/commands/earnings');
|
|
@@ -141,6 +144,14 @@ program
|
|
|
141
144
|
.description('๐ฐ View your earnings dashboard')
|
|
142
145
|
.action(earnings);
|
|
143
146
|
|
|
147
|
+
// ==================== MORNING COMMAND ====================
|
|
148
|
+
const { morning } = require('../src/commands/morning');
|
|
149
|
+
|
|
150
|
+
program
|
|
151
|
+
.command('morning')
|
|
152
|
+
.description('โ๏ธ Get daily research briefing')
|
|
153
|
+
.action(morning);
|
|
154
|
+
|
|
144
155
|
// ==================== OPEN COMMANDS ====================
|
|
145
156
|
const { openPage } = require('../src/commands/open');
|
|
146
157
|
|
|
@@ -150,7 +161,6 @@ program
|
|
|
150
161
|
.argument('[page]', 'Page to open (dashboard, earn, pricing, agents, etc.)')
|
|
151
162
|
.action(openPage);
|
|
152
163
|
|
|
153
|
-
// Shortcut commands
|
|
154
164
|
program
|
|
155
165
|
.command('dashboard')
|
|
156
166
|
.description('๐ Open web dashboard')
|
|
@@ -194,9 +204,11 @@ program.parse(process.argv);
|
|
|
194
204
|
if (!process.argv.slice(2).length) {
|
|
195
205
|
console.log(chalk.hex('#F24E1E')('\n๐ QUICK START:\n'));
|
|
196
206
|
console.log(chalk.white(' obx login # Authenticate'));
|
|
207
|
+
console.log(chalk.white(' obx morning # Daily briefing'));
|
|
197
208
|
console.log(chalk.white(' obx mission create # Create research'));
|
|
198
209
|
console.log(chalk.white(' obx publish <mission-id> # Publish & earn'));
|
|
199
210
|
console.log(chalk.white(' obx earnings # View earnings'));
|
|
211
|
+
console.log(chalk.white(' obx timeline <topic> # Generate timeline'));
|
|
200
212
|
console.log(chalk.white(' obx open # Open web pages\n'));
|
|
201
213
|
console.log(chalk.gray(' Run "obx --help" for all commands\n'));
|
|
202
214
|
}
|
package/package.json
CHANGED
package/src/commands/account.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const open = require('open');
|
|
3
3
|
const { getAuthToken, isAuthenticated } = require('../auth');
|
|
4
|
-
const { db,
|
|
4
|
+
const { db, getCurrentUserUid, getCurrentUserEmail } = require('../firebase');
|
|
5
5
|
const { PremiumSpinner, sleep } = require('../utils/display');
|
|
6
6
|
const { collection, query, where, getDocs } = require('firebase/firestore');
|
|
7
7
|
|
|
@@ -33,15 +33,6 @@ async function credits() {
|
|
|
33
33
|
console.log(chalk.white(` Current Balance: ${chalk.green(balance.toLocaleString() + ' RCC')}`));
|
|
34
34
|
console.log(chalk.white(` Status: ${balance > 100 ? chalk.green('โ Healthy') : chalk.yellow('โ Low')}`));
|
|
35
35
|
|
|
36
|
-
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
37
|
-
console.log(chalk.white.bold('๐ WHAT YOU CAN DO'));
|
|
38
|
-
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
39
|
-
|
|
40
|
-
console.log(chalk.white(` ๐ Literature Reviews: ${chalk.green(Math.floor(balance / 100))}`));
|
|
41
|
-
console.log(chalk.white(` ๐ Paper Analyses: ${chalk.green(Math.floor(balance / 40))}`));
|
|
42
|
-
console.log(chalk.white(` ๐ Quick Searches: ${chalk.green(Math.floor(balance / 10))}`));
|
|
43
|
-
console.log(chalk.white(` ๐งช Gap Discoveries: ${chalk.green(Math.floor(balance / 120))}`));
|
|
44
|
-
|
|
45
36
|
if (balance < 500) {
|
|
46
37
|
console.log(chalk.yellow('\n โ ๏ธ Your fuel is running low!'));
|
|
47
38
|
console.log(chalk.gray(' Run "obx buy" to purchase more credits\n'));
|
|
@@ -61,22 +52,24 @@ async function usage() {
|
|
|
61
52
|
return;
|
|
62
53
|
}
|
|
63
54
|
|
|
55
|
+
// Get user UID from stored token
|
|
56
|
+
const uid = getCurrentUserUid();
|
|
57
|
+
const email = getCurrentUserEmail();
|
|
58
|
+
|
|
59
|
+
if (!uid) {
|
|
60
|
+
console.error(chalk.red('โ Could not extract user ID from token.'));
|
|
61
|
+
console.error(chalk.gray('Please run: obx login'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
64
65
|
const spinner = new PremiumSpinner('Fetching usage statistics');
|
|
65
66
|
spinner.start();
|
|
66
67
|
|
|
67
68
|
try {
|
|
68
|
-
const user = auth.currentUser;
|
|
69
|
-
|
|
70
|
-
if (!user) {
|
|
71
|
-
spinner.fail('Not authenticated');
|
|
72
|
-
console.error(chalk.red('Please run: obx login'));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
69
|
// Fetch ALL missions (real data)
|
|
77
70
|
const allMissionsQuery = query(
|
|
78
71
|
collection(db, 'missions'),
|
|
79
|
-
where('uid', '==',
|
|
72
|
+
where('uid', '==', uid)
|
|
80
73
|
);
|
|
81
74
|
|
|
82
75
|
const allMissionsSnapshot = await getDocs(allMissionsQuery);
|
|
@@ -99,7 +92,7 @@ async function usage() {
|
|
|
99
92
|
// Fetch published missions for earnings
|
|
100
93
|
const publishedQuery = query(
|
|
101
94
|
collection(db, 'missions'),
|
|
102
|
-
where('uid', '==',
|
|
95
|
+
where('uid', '==', uid),
|
|
103
96
|
where('isPublished', '==', true)
|
|
104
97
|
);
|
|
105
98
|
|
|
@@ -122,7 +115,10 @@ async function usage() {
|
|
|
122
115
|
console.log(chalk.white.bold('๐ USAGE STATISTICS'));
|
|
123
116
|
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
124
117
|
|
|
125
|
-
console.log(chalk.
|
|
118
|
+
console.log(chalk.gray(` ๐ค User: ${chalk.white(email || 'Unknown')}`));
|
|
119
|
+
console.log(chalk.gray(` ๐ UID: ${chalk.gray(uid.substring(0, 16))}...`));
|
|
120
|
+
|
|
121
|
+
console.log(chalk.white(`\n ๐ฏ Total Missions: ${chalk.green(totalMissions)}`));
|
|
126
122
|
console.log(chalk.white(` โ
Completed: ${chalk.green(completedMissions)}`));
|
|
127
123
|
console.log(chalk.white(` โณ Active: ${chalk.yellow(activeMissions)}`));
|
|
128
124
|
console.log(chalk.white(` โฝ RCC Spent: ${chalk.green(totalRCCSpent.toLocaleString())}`));
|
package/src/commands/data.js
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const { createMission } = require('../api');
|
|
6
|
-
const { isAuthenticated } = require('../auth');
|
|
7
|
-
const {
|
|
8
|
-
generateMissionName,
|
|
9
|
-
displayMissionDashboard,
|
|
10
|
-
displayResearchScore,
|
|
11
|
-
displayArtifacts,
|
|
12
|
-
generateResearchScore,
|
|
13
|
-
generateArtifacts,
|
|
14
|
-
typeAIResponse,
|
|
15
|
-
PremiumSpinner,
|
|
16
|
-
sleep,
|
|
17
|
-
animateResearchSources
|
|
18
|
-
} = require('../utils/display');
|
|
19
|
-
|
|
20
|
-
const REPORTS_DIR = path.join(os.homedir(), 'obx-reports');
|
|
21
|
-
if (!fs.existsSync(REPORTS_DIR)) {
|
|
22
|
-
fs.mkdirSync(REPORTS_DIR, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function saveReport(topic, content, missionName) {
|
|
26
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
27
|
-
const sanitizedTopic = topic.replace(/[^a-z0-9]/gi, '_').substring(0, 50);
|
|
28
|
-
const filename = `${missionName.replace(/\s+/g, '_')}_${timestamp}.md`;
|
|
29
|
-
const filepath = path.join(REPORTS_DIR, filename);
|
|
30
|
-
|
|
31
|
-
fs.writeFileSync(filepath, content, 'utf8');
|
|
32
|
-
return filepath;
|
|
33
|
-
}
|
|
3
|
+
const { getAuthToken, isAuthenticated } = require('../auth');
|
|
4
|
+
const { PremiumSpinner, sleep, typeAIResponse } = require('../utils/display');
|
|
34
5
|
|
|
35
|
-
// ==================== DATASET ANALYSIS ====================
|
|
36
6
|
async function dataset(file) {
|
|
37
7
|
if (!isAuthenticated()) {
|
|
38
8
|
console.error(chalk.red('โ Not authenticated. Please run: obx login'));
|
|
@@ -44,69 +14,72 @@ async function dataset(file) {
|
|
|
44
14
|
return;
|
|
45
15
|
}
|
|
46
16
|
|
|
47
|
-
const missionName = generateMissionName();
|
|
48
|
-
|
|
49
17
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
50
|
-
console.log(chalk.white.bold(
|
|
51
|
-
console.log(chalk.hex('#F24E1E')('
|
|
52
|
-
console.log(chalk.gray(`File: ${file}`));
|
|
53
|
-
console.log(chalk.gray(`Type: Dataset Analysis (40-150 RCC)\n`));
|
|
18
|
+
console.log(chalk.white.bold('๐ DATASET ANALYSIS'));
|
|
19
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
54
20
|
|
|
55
|
-
|
|
21
|
+
console.log(chalk.white(` ๐ File: ${chalk.hex('#F24E1E')(file)}`));
|
|
22
|
+
|
|
23
|
+
const spinner = new PremiumSpinner('โจ Mission Genesis - Analyzing dataset');
|
|
56
24
|
spinner.start();
|
|
25
|
+
await sleep(500);
|
|
57
26
|
|
|
58
27
|
try {
|
|
59
|
-
|
|
28
|
+
const token = await getAuthToken();
|
|
29
|
+
const fileContent = fs.readFileSync(file, 'utf8').substring(0, 5000);
|
|
30
|
+
|
|
31
|
+
spinner.update('๐ Analyzing dataset');
|
|
60
32
|
await sleep(600);
|
|
61
|
-
|
|
62
|
-
spinner.update('Detecting patterns');
|
|
33
|
+
|
|
34
|
+
spinner.update('๐ Detecting patterns');
|
|
63
35
|
await sleep(600);
|
|
64
|
-
|
|
65
|
-
spinner.update('Calculating statistics');
|
|
36
|
+
|
|
37
|
+
spinner.update('๐ Calculating statistics');
|
|
66
38
|
await sleep(600);
|
|
67
|
-
|
|
68
|
-
// Read file content (first 5000 chars for large files)
|
|
69
|
-
const fileContent = fs.readFileSync(file, 'utf8').substring(0, 5000);
|
|
70
|
-
|
|
71
|
-
const result = await createMission(
|
|
72
|
-
`Analyze this dataset and provide:\n1. Statistical summary (mean, median, std dev, min, max)\n2. Pattern detection\n3. Anomaly identification\n4. Data quality assessment\n5. Visualization recommendations\n\nDataset content:\n${fileContent}`,
|
|
73
|
-
'DATASET_ANALYSIS'
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
spinner.succeed('Dataset analysis complete');
|
|
77
|
-
|
|
78
|
-
console.log(chalk.gray(`\n๐ Model: ${result.model}`));
|
|
79
|
-
console.log(chalk.gray(`๐ฐ RCC Cost: ${result.rccCost}`));
|
|
80
|
-
console.log(chalk.gray(`๐ณ Remaining Balance: ${result.rccBalance} RCC\n`));
|
|
81
39
|
|
|
82
|
-
|
|
83
|
-
await
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
40
|
+
spinner.update('โ ๏ธ Checking for anomalies');
|
|
41
|
+
await sleep(400);
|
|
42
|
+
|
|
43
|
+
const response = await fetch('https://obxvisionassistant-yyedhmslhq-uc.a.run.app', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'Authorization': `Bearer ${token}`
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
taskType: 'DATASET_ANALYSIS',
|
|
51
|
+
message: fileContent,
|
|
52
|
+
model: 'deep'
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorData = await response.json();
|
|
58
|
+
throw new Error(errorData.error || 'Failed to analyze dataset');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = await response.json();
|
|
97
62
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
63
|
+
spinner.succeed('โ Dataset analysis complete (40-150 RCC)');
|
|
64
|
+
|
|
65
|
+
await typeAIResponse(data.response || 'Analysis data not available.');
|
|
66
|
+
|
|
67
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
68
|
+
console.log(chalk.white.bold('๐ฆ ARTIFACTS GENERATED'));
|
|
69
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
102
70
|
|
|
71
|
+
console.log(chalk.green(' โ Statistical Summary'));
|
|
72
|
+
console.log(chalk.green(' โ Pattern Report'));
|
|
73
|
+
console.log(chalk.green(' โ Anomaly Detection'));
|
|
74
|
+
|
|
75
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
76
|
+
|
|
103
77
|
} catch (error) {
|
|
104
78
|
spinner.fail('Failed to analyze dataset');
|
|
105
|
-
console.error(chalk.red(error.
|
|
79
|
+
console.error(chalk.red(error.message));
|
|
106
80
|
}
|
|
107
81
|
}
|
|
108
82
|
|
|
109
|
-
// ==================== CODE REVIEW ====================
|
|
110
83
|
async function code(directory = '.') {
|
|
111
84
|
if (!isAuthenticated()) {
|
|
112
85
|
console.error(chalk.red('โ Not authenticated. Please run: obx login'));
|
|
@@ -118,153 +91,80 @@ async function code(directory = '.') {
|
|
|
118
91
|
return;
|
|
119
92
|
}
|
|
120
93
|
|
|
121
|
-
const missionName = generateMissionName();
|
|
122
|
-
|
|
123
94
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
124
|
-
console.log(chalk.white.bold(
|
|
125
|
-
console.log(chalk.hex('#F24E1E')('
|
|
126
|
-
|
|
127
|
-
console.log(chalk.
|
|
95
|
+
console.log(chalk.white.bold('๐ป CODE REVIEW'));
|
|
96
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
97
|
+
|
|
98
|
+
console.log(chalk.white(` ๐ Directory: ${chalk.hex('#F24E1E')(directory)}`));
|
|
128
99
|
|
|
129
|
-
const spinner = new PremiumSpinner('
|
|
100
|
+
const spinner = new PremiumSpinner('โจ Mission Aurora - Reviewing code');
|
|
130
101
|
spinner.start();
|
|
102
|
+
await sleep(500);
|
|
131
103
|
|
|
132
104
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
spinner.update('Security audit');
|
|
137
|
-
await sleep(800);
|
|
138
|
-
|
|
139
|
-
spinner.update('Performance review');
|
|
105
|
+
const token = await getAuthToken();
|
|
106
|
+
|
|
107
|
+
spinner.update('๐ Scanning repository');
|
|
140
108
|
await sleep(600);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
109
|
+
|
|
110
|
+
spinner.update('๐ Analyzing architecture');
|
|
111
|
+
await sleep(600);
|
|
112
|
+
|
|
113
|
+
spinner.update('๐ Security audit');
|
|
114
|
+
await sleep(600);
|
|
115
|
+
|
|
116
|
+
spinner.update('โก Performance review');
|
|
117
|
+
await sleep(400);
|
|
118
|
+
|
|
119
|
+
// Read code files (limit to 5000 chars)
|
|
148
120
|
let codeContent = '';
|
|
149
|
-
|
|
121
|
+
const files = fs.readdirSync(directory);
|
|
122
|
+
for (const file of files.slice(0, 10)) {
|
|
150
123
|
const filePath = path.join(directory, file);
|
|
151
|
-
if (fs.statSync(filePath).isFile()) {
|
|
152
|
-
codeContent += `\n
|
|
153
|
-
codeContent += fs.readFileSync(filePath, 'utf8').substring(0,
|
|
124
|
+
if (fs.statSync(filePath).isFile() && file.match(/\.(js|ts|py|java|cpp|go|rs)$/)) {
|
|
125
|
+
codeContent += `\n// File: ${file}\n`;
|
|
126
|
+
codeContent += fs.readFileSync(filePath, 'utf8').substring(0, 1000);
|
|
154
127
|
}
|
|
155
128
|
}
|
|
156
|
-
|
|
157
|
-
const result = await createMission(
|
|
158
|
-
`Review this code repository and provide:\n1. Architecture analysis\n2. Security vulnerabilities\n3. Performance issues\n4. Code quality assessment\n5. Optimization suggestions\n6. Best practices recommendations\n\nCode:\n${codeContent}`,
|
|
159
|
-
'CODE_REVIEW'
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
spinner.succeed('Code review complete');
|
|
163
|
-
|
|
164
|
-
console.log(chalk.gray(`\n๐ Model: ${result.model}`));
|
|
165
|
-
console.log(chalk.gray(`๐ฐ RCC Cost: ${result.rccCost}`));
|
|
166
|
-
console.log(chalk.gray(`๐ณ Remaining Balance: ${result.rccBalance} RCC\n`));
|
|
167
|
-
|
|
168
|
-
// Display the review with typing animation
|
|
169
|
-
await typeAIResponse(result.response);
|
|
170
|
-
|
|
171
|
-
// Research score
|
|
172
|
-
const score = generateResearchScore(result.response);
|
|
173
|
-
await displayResearchScore(score);
|
|
174
|
-
|
|
175
|
-
// Artifacts
|
|
176
|
-
const artifacts = [
|
|
177
|
-
{ icon: '๐๏ธ', name: 'Architecture Report' },
|
|
178
|
-
{ icon: '๐', name: 'Security Audit' },
|
|
179
|
-
{ icon: 'โก', name: 'Performance Analysis' },
|
|
180
|
-
{ icon: '๐ก', name: 'Optimization Suggestions' }
|
|
181
|
-
];
|
|
182
|
-
await displayArtifacts(artifacts);
|
|
183
|
-
|
|
184
|
-
// Save report
|
|
185
|
-
const filepath = saveReport(directory, result.response, missionName);
|
|
186
|
-
console.log(chalk.green(`โ Report saved to: ${filepath}`));
|
|
187
|
-
console.log(chalk.gray('\nView with: cat ' + filepath + '\n'));
|
|
188
|
-
|
|
189
|
-
} catch (error) {
|
|
190
|
-
spinner.fail('Failed to review code');
|
|
191
|
-
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
129
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
130
|
+
const response = await fetch('https://obxvisionassistant-yyedhmslhq-uc.a.run.app', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'Authorization': `Bearer ${token}`
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
taskType: 'CODE_REVIEW',
|
|
138
|
+
message: codeContent,
|
|
139
|
+
model: 'deep'
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const errorData = await response.json();
|
|
145
|
+
throw new Error(errorData.error || 'Failed to review code');
|
|
146
|
+
}
|
|
206
147
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log(chalk.white.bold(`โจ ${missionName}`));
|
|
211
|
-
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
212
|
-
console.log(chalk.gray(`File: ${file}`));
|
|
213
|
-
console.log(chalk.gray(`Type: Medical Imaging Analysis (30-120 RCC)\n`));
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
|
|
150
|
+
spinner.succeed('โ Code review complete (40-300 RCC)');
|
|
214
151
|
|
|
215
|
-
|
|
216
|
-
spinner.start();
|
|
152
|
+
await typeAIResponse(data.response || 'Review data not available.');
|
|
217
153
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
spinner.update('Detecting anomalies');
|
|
223
|
-
await sleep(800);
|
|
224
|
-
|
|
225
|
-
spinner.update('Comparing with reference database');
|
|
226
|
-
await sleep(600);
|
|
154
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
155
|
+
console.log(chalk.white.bold('๐ฆ ARTIFACTS GENERATED'));
|
|
156
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
227
157
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`Analyze this medical imaging file and provide a comprehensive research-oriented analysis:\n\nFile: ${path.basename(file)}\nFormat: ${fileExt}\nSize: ${stats.size} bytes\n\nPlease provide:\n1. Image type identification (X-ray, MRI, CT, Ultrasound, etc.)\n2. Anatomical region identification\n3. Pattern detection and findings\n4. Potential anomalies or areas of interest\n5. Comparative analysis with typical presentations\n6. Research implications and suggested further investigation\n7. Confidence level and limitations\n\nNote: This is for research purposes only, not clinical diagnosis.`,
|
|
234
|
-
'MEDICAL_ANALYSIS'
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
spinner.succeed('Medical analysis complete');
|
|
238
|
-
|
|
239
|
-
console.log(chalk.gray(`\n๐ Model: ${result.model}`));
|
|
240
|
-
console.log(chalk.gray(`๐ฐ RCC Cost: ${result.rccCost}`));
|
|
241
|
-
console.log(chalk.gray(`๐ณ Remaining Balance: ${result.rccBalance} RCC\n`));
|
|
158
|
+
console.log(chalk.green(' โ Architecture Report'));
|
|
159
|
+
console.log(chalk.green(' โ Security Audit'));
|
|
160
|
+
console.log(chalk.green(' โ Optimization Suggestions'));
|
|
161
|
+
|
|
162
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
242
163
|
|
|
243
|
-
// ๐ฅ THIS WAS MISSING - Display the analysis with typing animation
|
|
244
|
-
await typeAIResponse(result.response);
|
|
245
|
-
|
|
246
|
-
// Research score
|
|
247
|
-
const score = generateResearchScore(result.response);
|
|
248
|
-
await displayResearchScore(score);
|
|
249
|
-
|
|
250
|
-
// Artifacts
|
|
251
|
-
const artifacts = [
|
|
252
|
-
{ icon: '๐ฅ', name: 'Pattern Detection Report' },
|
|
253
|
-
{ icon: '๐', name: 'Anomaly Analysis' },
|
|
254
|
-
{ icon: '๐', name: 'Comparative Analysis' },
|
|
255
|
-
{ icon: '๐', name: 'Research Implications' }
|
|
256
|
-
];
|
|
257
|
-
await displayArtifacts(artifacts);
|
|
258
|
-
|
|
259
|
-
// Save report
|
|
260
|
-
const filepath = saveReport(file, result.response, missionName);
|
|
261
|
-
console.log(chalk.green(`โ Report saved to: ${filepath}`));
|
|
262
|
-
console.log(chalk.gray('\nView with: cat ' + filepath + '\n'));
|
|
263
|
-
|
|
264
164
|
} catch (error) {
|
|
265
|
-
spinner.fail('Failed to
|
|
266
|
-
console.error(chalk.red(error.
|
|
165
|
+
spinner.fail('Failed to review code');
|
|
166
|
+
console.error(chalk.red(error.message));
|
|
267
167
|
}
|
|
268
168
|
}
|
|
269
169
|
|
|
270
|
-
module.exports = { dataset, code
|
|
170
|
+
module.exports = { dataset, code };
|
package/src/commands/earnings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const {
|
|
3
|
-
const { db,
|
|
2
|
+
const { isAuthenticated } = require('../auth');
|
|
3
|
+
const { db, getCurrentUserUid, getCurrentUserEmail } = require('../firebase');
|
|
4
4
|
const { PremiumSpinner, sleep } = require('../utils/display');
|
|
5
5
|
const { collection, query, where, orderBy, limit, getDocs } = require('firebase/firestore');
|
|
6
6
|
|
|
@@ -10,22 +10,22 @@ async function earnings() {
|
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const uid = getCurrentUserUid();
|
|
14
|
+
const email = getCurrentUserEmail();
|
|
15
|
+
|
|
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
|
+
|
|
13
22
|
const spinner = new PremiumSpinner('Fetching your earnings data');
|
|
14
23
|
spinner.start();
|
|
15
24
|
|
|
16
25
|
try {
|
|
17
|
-
const user = auth.currentUser;
|
|
18
|
-
|
|
19
|
-
if (!user) {
|
|
20
|
-
spinner.fail('Not authenticated');
|
|
21
|
-
console.error(chalk.red('Please run: obx login'));
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Fetch real published missions with earnings data
|
|
26
26
|
const publishedQuery = query(
|
|
27
27
|
collection(db, 'missions'),
|
|
28
|
-
where('uid', '==',
|
|
28
|
+
where('uid', '==', uid),
|
|
29
29
|
where('isPublished', '==', true),
|
|
30
30
|
orderBy('publishedAt', 'desc'),
|
|
31
31
|
limit(100)
|
|
@@ -43,13 +43,11 @@ async function earnings() {
|
|
|
43
43
|
|
|
44
44
|
spinner.succeed('Earnings data loaded');
|
|
45
45
|
|
|
46
|
-
// Calculate real earnings from actual data
|
|
47
46
|
const totalViews = publishedMissions.reduce((sum, m) => sum + (m.views || 0), 0);
|
|
48
47
|
const totalDownloads = publishedMissions.reduce((sum, m) => sum + (m.downloads || 0), 0);
|
|
49
48
|
const totalCitations = publishedMissions.reduce((sum, m) => sum + (m.citations || 0), 0);
|
|
50
49
|
const totalEarnings = publishedMissions.reduce((sum, m) => sum + (m.earnings || 0), 0);
|
|
51
50
|
|
|
52
|
-
// Calculate monthly breakdown
|
|
53
51
|
const now = new Date();
|
|
54
52
|
const thisMonth = now.getMonth();
|
|
55
53
|
const thisYear = now.getFullYear();
|
|
@@ -73,8 +71,10 @@ async function earnings() {
|
|
|
73
71
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
74
72
|
console.log(chalk.white.bold('๐ฐ EARNINGS DASHBOARD'));
|
|
75
73
|
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
74
|
+
|
|
75
|
+
console.log(chalk.gray(` ๐ค User: ${chalk.white(email || 'Unknown')}`));
|
|
76
76
|
|
|
77
|
-
console.log(chalk.white.bold('๐ OVERVIEW'));
|
|
77
|
+
console.log(chalk.white.bold('\n\n๐ OVERVIEW'));
|
|
78
78
|
console.log(chalk.gray('โ'.repeat(60)));
|
|
79
79
|
console.log(chalk.white(`\n ๐ฐ Total Earnings: ${chalk.green('โน' + totalEarnings.toLocaleString())}`));
|
|
80
80
|
console.log(chalk.white(` ๐
This Month: ${chalk.green('โน' + thisMonthEarnings.toLocaleString())}`));
|
|
@@ -93,12 +93,6 @@ async function earnings() {
|
|
|
93
93
|
console.log(chalk.white(` ๐ต Average Per View: ${chalk.green('โน' + averagePerView.toFixed(3))}`));
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
console.log(chalk.white.bold('\n\n๐ณ PAYOUT DETAILS'));
|
|
97
|
-
console.log(chalk.gray('โ'.repeat(60)));
|
|
98
|
-
console.log(chalk.white(`\n ๐ Revenue Share: ${chalk.green('80%')}`));
|
|
99
|
-
console.log(chalk.white(` ๐
Next Payout: ${chalk.green('1st of next month')}`));
|
|
100
|
-
console.log(chalk.white(` ๐ณ Payout Method: ${chalk.green('Bank Transfer / UPI')}`));
|
|
101
|
-
|
|
102
96
|
if (publishedMissions.length > 0) {
|
|
103
97
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
104
98
|
console.log(chalk.white.bold('๐ YOUR PUBLISHED RESEARCH'));
|
|
@@ -112,18 +106,13 @@ async function earnings() {
|
|
|
112
106
|
console.log(chalk.white(` ๐ฐ Earnings: ${chalk.green('โน' + (m.earnings || 0).toLocaleString())}`));
|
|
113
107
|
console.log(chalk.gray(` ๐
Published: ${publishedDate}`));
|
|
114
108
|
});
|
|
109
|
+
} else {
|
|
110
|
+
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'));
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
118
115
|
|
|
119
|
-
console.log(chalk.white.bold('๐ก TIPS TO INCREASE EARNINGS'));
|
|
120
|
-
console.log(chalk.gray('โ'.repeat(60)));
|
|
121
|
-
console.log(chalk.white('\n โ Publish more research to increase views'));
|
|
122
|
-
console.log(chalk.white(' โ Share your research on social media'));
|
|
123
|
-
console.log(chalk.white(' โ Use relevant tags for better discoverability'));
|
|
124
|
-
console.log(chalk.white(' โ Create high-quality, comprehensive research'));
|
|
125
|
-
console.log(chalk.white(' โ Update research regularly with new findings\n'));
|
|
126
|
-
|
|
127
116
|
} catch (error) {
|
|
128
117
|
spinner.fail('Failed to fetch earnings data');
|
|
129
118
|
console.error(chalk.red(error.message));
|
package/src/commands/morning.js
CHANGED
|
@@ -1,94 +1,151 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const { getAuthToken } = require('../auth');
|
|
3
2
|
const { isAuthenticated } = require('../auth');
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
sleep
|
|
9
|
-
} = require('../utils/display');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Fetch and display morning briefing
|
|
13
|
-
*/
|
|
3
|
+
const { db, getCurrentUserUid, getCurrentUserEmail } = require('../firebase');
|
|
4
|
+
const { PremiumSpinner, sleep } = require('../utils/display');
|
|
5
|
+
const { collection, query, where, orderBy, limit, getDocs } = require('firebase/firestore');
|
|
6
|
+
|
|
14
7
|
async function morning() {
|
|
15
8
|
if (!isAuthenticated()) {
|
|
16
9
|
console.error(chalk.red('โ Not authenticated. Please run: obx login'));
|
|
17
10
|
return;
|
|
18
11
|
}
|
|
19
12
|
|
|
20
|
-
const
|
|
13
|
+
const uid = getCurrentUserUid();
|
|
14
|
+
const email = getCurrentUserEmail();
|
|
15
|
+
|
|
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 daily briefing');
|
|
21
23
|
spinner.start();
|
|
22
24
|
|
|
23
25
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
// Get today's date
|
|
27
|
+
const today = new Date();
|
|
28
|
+
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
29
|
+
const yesterdayStart = new Date(todayStart);
|
|
30
|
+
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
|
|
31
|
+
|
|
32
|
+
// Fetch active missions
|
|
33
|
+
const activeQuery = query(
|
|
34
|
+
collection(db, 'missions'),
|
|
35
|
+
where('uid', '==', uid),
|
|
36
|
+
where('status', 'in', ['running', 'pending'])
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const activeSnapshot = await getDocs(activeQuery);
|
|
40
|
+
const activeMissions = [];
|
|
41
|
+
activeSnapshot.forEach(doc => {
|
|
42
|
+
activeMissions.push({ id: doc.id, ...doc.data() });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Fetch recent completed missions (last 7 days)
|
|
46
|
+
const sevenDaysAgo = new Date();
|
|
47
|
+
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
const recentQuery = query(
|
|
50
|
+
collection(db, 'missions'),
|
|
51
|
+
where('uid', '==', uid),
|
|
52
|
+
where('status', '==', 'completed'),
|
|
53
|
+
orderBy('createdAt', 'desc'),
|
|
54
|
+
limit(10)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const recentSnapshot = await getDocs(recentQuery);
|
|
58
|
+
const recentMissions = [];
|
|
59
|
+
recentSnapshot.forEach(doc => {
|
|
60
|
+
recentMissions.push({ id: doc.id, ...doc.data() });
|
|
42
61
|
});
|
|
62
|
+
|
|
63
|
+
// Fetch published missions for earnings
|
|
64
|
+
const publishedQuery = query(
|
|
65
|
+
collection(db, 'missions'),
|
|
66
|
+
where('uid', '==', uid),
|
|
67
|
+
where('isPublished', '==', true)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const publishedSnapshot = await getDocs(publishedQuery);
|
|
71
|
+
const publishedMissions = [];
|
|
72
|
+
publishedSnapshot.forEach(doc => {
|
|
73
|
+
publishedMissions.push({ id: doc.id, ...doc.data() });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const totalViews = publishedMissions.reduce((sum, m) => sum + (m.views || 0), 0);
|
|
77
|
+
const totalEarnings = publishedMissions.reduce((sum, m) => sum + (m.earnings || 0), 0);
|
|
78
|
+
|
|
79
|
+
spinner.succeed('Daily briefing loaded');
|
|
80
|
+
|
|
81
|
+
// Display the briefing
|
|
82
|
+
const dateString = today.toLocaleDateString('en-US', {
|
|
83
|
+
weekday: 'long',
|
|
84
|
+
year: 'numeric',
|
|
85
|
+
month: 'long',
|
|
86
|
+
day: 'numeric'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
90
|
+
console.log(chalk.white.bold('โ๏ธ GOOD MORNING, RESEARCHER!'));
|
|
91
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
43
92
|
|
|
44
|
-
|
|
45
|
-
|
|
93
|
+
console.log(chalk.gray(` ๐ค ${chalk.white(email || 'Unknown')}`));
|
|
94
|
+
console.log(chalk.gray(` ๐
${chalk.white(dateString)}`));
|
|
95
|
+
|
|
96
|
+
// Active Missions
|
|
97
|
+
if (activeMissions.length > 0) {
|
|
98
|
+
console.log(chalk.white.bold('\n\n๐ฏ ACTIVE MISSIONS'));
|
|
99
|
+
console.log(chalk.gray('โ'.repeat(60)));
|
|
100
|
+
|
|
101
|
+
activeMissions.forEach((m, i) => {
|
|
102
|
+
const topic = (m.message || 'No topic').substring(0, 50);
|
|
103
|
+
console.log(chalk.white(`\n โจ ${chalk.hex('#F24E1E')(m.id)}`));
|
|
104
|
+
console.log(chalk.white(` Topic: ${topic}`));
|
|
105
|
+
console.log(chalk.yellow(` Status: ${m.status}`));
|
|
106
|
+
});
|
|
46
107
|
}
|
|
108
|
+
|
|
109
|
+
// Recent Completions
|
|
110
|
+
if (recentMissions.length > 0) {
|
|
111
|
+
console.log(chalk.white.bold('\n\nโ
RECENTLY COMPLETED'));
|
|
112
|
+
console.log(chalk.gray('โ'.repeat(60)));
|
|
113
|
+
|
|
114
|
+
recentMissions.slice(0, 5).forEach((m, i) => {
|
|
115
|
+
const topic = (m.message || 'No topic').substring(0, 50);
|
|
116
|
+
const date = m.createdAt?.toDate?.().toLocaleDateString() || 'N/A';
|
|
117
|
+
console.log(chalk.white(`\n ${i + 1}. ${topic}`));
|
|
118
|
+
console.log(chalk.gray(` Completed: ${date}`));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Earnings Summary
|
|
123
|
+
if (publishedMissions.length > 0) {
|
|
124
|
+
console.log(chalk.white.bold('\n\n๐ฐ EARNINGS SUMMARY'));
|
|
125
|
+
console.log(chalk.gray('โ'.repeat(60)));
|
|
126
|
+
|
|
127
|
+
console.log(chalk.white(`\n ๐ค Published Research: ${chalk.green(publishedMissions.length)}`));
|
|
128
|
+
console.log(chalk.white(` ๐ Total Views: ${chalk.green(totalViews.toLocaleString())}`));
|
|
129
|
+
console.log(chalk.white(` ๐ฐ Total Earnings: ${chalk.green('โน' + totalEarnings.toLocaleString())}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Recommendations
|
|
133
|
+
console.log(chalk.white.bold('\n\n๐ก RECOMMENDATIONS'));
|
|
134
|
+
console.log(chalk.gray('โ'.repeat(60)));
|
|
47
135
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
136
|
+
console.log(chalk.white('\n โธ Check active missions with "obx mission status"'));
|
|
137
|
+
console.log(chalk.white(' โธ Publish completed research with "obx publish"'));
|
|
138
|
+
console.log(chalk.white(' โธ View earnings with "obx earnings"'));
|
|
139
|
+
console.log(chalk.white(' โธ Generate new research with "obx mission create"'));
|
|
140
|
+
|
|
141
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
142
|
+
console.log(chalk.gray('Free โข No RCC cost'));
|
|
143
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
144
|
+
|
|
55
145
|
} catch (error) {
|
|
56
|
-
spinner.fail('Failed to fetch
|
|
57
|
-
|
|
58
|
-
// Fallback: show local briefing
|
|
59
|
-
console.log(chalk.gray('\n Showing local briefing...\n'));
|
|
60
|
-
await displayLocalBriefing();
|
|
146
|
+
spinner.fail('Failed to fetch daily briefing');
|
|
147
|
+
console.error(chalk.red(error.message));
|
|
61
148
|
}
|
|
62
149
|
}
|
|
63
150
|
|
|
64
|
-
/**
|
|
65
|
-
* Display local briefing when API is unavailable
|
|
66
|
-
*/
|
|
67
|
-
async function displayLocalBriefing() {
|
|
68
|
-
const today = new Date().toLocaleDateString('en-US', {
|
|
69
|
-
weekday: 'long',
|
|
70
|
-
year: 'numeric',
|
|
71
|
-
month: 'long',
|
|
72
|
-
day: 'numeric'
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const briefing = {
|
|
76
|
-
date: today,
|
|
77
|
-
updates: [
|
|
78
|
-
{ type: 'info', text: 'No new updates since last check' },
|
|
79
|
-
{ type: 'info', text: 'All missions are up to date' }
|
|
80
|
-
],
|
|
81
|
-
missions: [
|
|
82
|
-
{ name: 'Mission Atlas', status: 'Complete', progress: 100 }
|
|
83
|
-
],
|
|
84
|
-
recommendations: [
|
|
85
|
-
'Run "obx morning" daily to stay updated',
|
|
86
|
-
'Check mission health with "obx mission status"',
|
|
87
|
-
'Explore new topics with "obx literature [topic]"'
|
|
88
|
-
]
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
await displayMorningBriefing(briefing);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
151
|
module.exports = { morning };
|
package/src/commands/publish.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const inquirer = require('inquirer');
|
|
3
3
|
const { getAuthToken, isAuthenticated } = require('../auth');
|
|
4
|
-
const { db,
|
|
4
|
+
const { db, getCurrentUserUid, getCurrentUserEmail } = require('../firebase');
|
|
5
5
|
const { PremiumSpinner, sleep } = require('../utils/display');
|
|
6
6
|
const { collection, query, where, orderBy, limit, getDocs, doc, updateDoc } = require('firebase/firestore');
|
|
7
7
|
|
|
@@ -11,30 +11,31 @@ async function publish(missionId) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const uid = getCurrentUserUid();
|
|
15
|
+
const email = getCurrentUserEmail();
|
|
16
|
+
|
|
17
|
+
if (!uid) {
|
|
18
|
+
console.error(chalk.red('โ Could not extract user ID from token.'));
|
|
19
|
+
console.error(chalk.gray('Please run: obx login'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
15
24
|
console.log(chalk.white.bold('๐ค PUBLISH RESEARCH'));
|
|
16
25
|
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
26
|
+
|
|
27
|
+
console.log(chalk.gray(` ๐ค User: ${chalk.white(email || 'Unknown')}`));
|
|
17
28
|
|
|
18
|
-
// If no mission ID provided, fetch real missions from Firestore
|
|
19
29
|
if (!missionId) {
|
|
20
|
-
console.log(chalk.gray('
|
|
30
|
+
console.log(chalk.gray('\nFetching your completed missions...\n'));
|
|
21
31
|
|
|
22
32
|
const spinner = new PremiumSpinner('Loading missions from database');
|
|
23
33
|
spinner.start();
|
|
24
34
|
|
|
25
35
|
try {
|
|
26
|
-
const user = auth.currentUser;
|
|
27
|
-
|
|
28
|
-
if (!user) {
|
|
29
|
-
spinner.fail('Not authenticated');
|
|
30
|
-
console.error(chalk.red('Please run: obx login'));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Fetch real completed missions from Firestore
|
|
35
36
|
const missionsQuery = query(
|
|
36
37
|
collection(db, 'missions'),
|
|
37
|
-
where('uid', '==',
|
|
38
|
+
where('uid', '==', uid),
|
|
38
39
|
where('status', '==', 'completed'),
|
|
39
40
|
where('isPublished', '==', false),
|
|
40
41
|
orderBy('createdAt', 'desc'),
|
|
@@ -92,7 +93,6 @@ async function publish(missionId) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
// Publish the mission
|
|
96
96
|
const spinner = new PremiumSpinner('Preparing to publish research');
|
|
97
97
|
spinner.start();
|
|
98
98
|
await sleep(500);
|
|
@@ -101,7 +101,6 @@ async function publish(missionId) {
|
|
|
101
101
|
spinner.update('Fetching mission data');
|
|
102
102
|
const missionRef = doc(db, 'missions', missionId);
|
|
103
103
|
|
|
104
|
-
// Get the mission document
|
|
105
104
|
const missionSnap = await getDocs(query(collection(db, 'missions'), where('__name__', '==', missionId)));
|
|
106
105
|
|
|
107
106
|
if (missionSnap.empty) {
|
|
@@ -115,7 +114,6 @@ async function publish(missionId) {
|
|
|
115
114
|
spinner.update('Generating SEO-optimized page');
|
|
116
115
|
await sleep(500);
|
|
117
116
|
|
|
118
|
-
// Generate slug from mission topic
|
|
119
117
|
const slug = (missionData.message || missionId)
|
|
120
118
|
.toLowerCase()
|
|
121
119
|
.replace(/[^a-z0-9]+/g, '-')
|
|
@@ -131,7 +129,6 @@ async function publish(missionId) {
|
|
|
131
129
|
spinner.update('Enabling revenue tracking');
|
|
132
130
|
await sleep(400);
|
|
133
131
|
|
|
134
|
-
// Update mission in Firestore
|
|
135
132
|
await updateDoc(missionRef, {
|
|
136
133
|
isPublished: true,
|
|
137
134
|
publishedAt: new Date(),
|
|
@@ -140,8 +137,8 @@ async function publish(missionId) {
|
|
|
140
137
|
downloads: 0,
|
|
141
138
|
citations: 0,
|
|
142
139
|
earnings: 0,
|
|
143
|
-
authorName:
|
|
144
|
-
authorEmail:
|
|
140
|
+
authorName: email || 'Anonymous Researcher',
|
|
141
|
+
authorEmail: email || 'unknown@example.com'
|
|
145
142
|
});
|
|
146
143
|
|
|
147
144
|
spinner.succeed('Research published successfully!');
|
|
@@ -158,18 +155,7 @@ async function publish(missionId) {
|
|
|
158
155
|
console.log(chalk.white(`๐ Google Indexing: ${chalk.green('Requested')} (24 hours)`));
|
|
159
156
|
console.log(chalk.white(`๐ Analytics: ${chalk.green('Enabled')}`));
|
|
160
157
|
|
|
161
|
-
console.log(chalk.hex('#F24E1E')('\n
|
|
162
|
-
console.log(chalk.white.bold('๐ฐ START EARNING'));
|
|
163
|
-
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
164
|
-
|
|
165
|
-
console.log(chalk.gray('Your research is now live! Here\'s what happens next:'));
|
|
166
|
-
console.log(chalk.white('\n โ Google indexes your research within 24 hours'));
|
|
167
|
-
console.log(chalk.white(' โ Readers discover it through search'));
|
|
168
|
-
console.log(chalk.white(' โ Ads are displayed to readers'));
|
|
169
|
-
console.log(chalk.white(' โ You earn 80% of all ad revenue'));
|
|
170
|
-
console.log(chalk.white(' โ Monthly payouts to your account\n'));
|
|
171
|
-
|
|
172
|
-
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
158
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
173
159
|
|
|
174
160
|
} catch (error) {
|
|
175
161
|
spinner.fail('Failed to publish research');
|
package/src/commands/timeline.js
CHANGED
|
@@ -1,152 +1,91 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const {
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
generateMissionName,
|
|
6
|
-
displayTimeline,
|
|
7
|
-
PremiumSpinner,
|
|
8
|
-
sleep
|
|
9
|
-
} = require('../utils/display');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Generate research timeline for a topic
|
|
13
|
-
*/
|
|
2
|
+
const { getAuthToken, isAuthenticated } = require('../auth');
|
|
3
|
+
const { PremiumSpinner, sleep, typeAIResponse } = require('../utils/display');
|
|
4
|
+
|
|
14
5
|
async function timeline(topic) {
|
|
15
6
|
if (!isAuthenticated()) {
|
|
16
7
|
console.error(chalk.red('โ Not authenticated. Please run: obx login'));
|
|
17
8
|
return;
|
|
18
9
|
}
|
|
19
10
|
|
|
20
|
-
if (!topic
|
|
21
|
-
console.error(chalk.red('โ Please provide a research topic'));
|
|
22
|
-
console.log(chalk.gray('
|
|
11
|
+
if (!topic) {
|
|
12
|
+
console.error(chalk.red('โ Please provide a research topic.'));
|
|
13
|
+
console.log(chalk.gray('Usage: obx timeline "quantum computing"\n'));
|
|
23
14
|
return;
|
|
24
15
|
}
|
|
25
16
|
|
|
26
|
-
const missionName = generateMissionName();
|
|
27
|
-
|
|
28
17
|
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
29
|
-
console.log(chalk.white.bold(
|
|
30
|
-
console.log(chalk.hex('#F24E1E')('
|
|
31
|
-
|
|
32
|
-
console.log(chalk.
|
|
18
|
+
console.log(chalk.white.bold('๐
RESEARCH TIMELINE GENERATOR'));
|
|
19
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
20
|
+
|
|
21
|
+
console.log(chalk.white(` ๐ Topic: ${chalk.hex('#F24E1E')(topic)}`));
|
|
22
|
+
console.log(chalk.white(` ๐ฐ Cost: ${chalk.green('80 RCC')}`));
|
|
33
23
|
|
|
34
|
-
const spinner = new PremiumSpinner('
|
|
24
|
+
const spinner = new PremiumSpinner('โจ Mission Polaris - Generating timeline');
|
|
35
25
|
spinner.start();
|
|
26
|
+
await sleep(500);
|
|
36
27
|
|
|
37
28
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
spinner.update('Identifying key milestones');
|
|
42
|
-
await sleep(800);
|
|
43
|
-
|
|
44
|
-
spinner.update('Building timeline graph');
|
|
29
|
+
const token = await getAuthToken();
|
|
30
|
+
|
|
31
|
+
spinner.update('๐ Searching historical research');
|
|
45
32
|
await sleep(600);
|
|
46
|
-
|
|
47
|
-
spinner.update('Analyzing
|
|
33
|
+
|
|
34
|
+
spinner.update('๐ Analyzing foundational papers');
|
|
48
35
|
await sleep(600);
|
|
49
|
-
|
|
50
|
-
// Call backend API
|
|
51
|
-
const result = await createMission(
|
|
52
|
-
`Generate a comprehensive research timeline for: ${topic}.
|
|
53
|
-
Include major papers, breakthroughs, patents, and milestones from the earliest research to present day.
|
|
54
|
-
Format as JSON array with objects containing: year, title, description, impact (High/Medium/Low), type (paper/patent/breakthrough/milestone).
|
|
55
|
-
Sort chronologically. Include 8-12 key events.`,
|
|
56
|
-
'RESEARCH_TIMELINE'
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
spinner.succeed('Timeline generated');
|
|
60
|
-
|
|
61
|
-
console.log(chalk.gray(`\n๐ Model: ${result.model}`));
|
|
62
|
-
console.log(chalk.gray(`๐ฐ RCC Cost: ${result.rccCost}`));
|
|
63
|
-
console.log(chalk.gray(`๐ณ Remaining Balance: ${result.rccBalance} RCC\n`));
|
|
64
36
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
37
|
+
spinner.update('๐บ๏ธ Mapping field evolution');
|
|
38
|
+
await sleep(600);
|
|
39
|
+
|
|
40
|
+
spinner.update('๐ Identifying key milestones');
|
|
41
|
+
await sleep(600);
|
|
42
|
+
|
|
43
|
+
spinner.update('โ๏ธ Compiling timeline');
|
|
44
|
+
await sleep(400);
|
|
45
|
+
|
|
46
|
+
// Call the AI backend
|
|
47
|
+
const response = await fetch('https://obxvisionassistant-yyedhmslhq-uc.a.run.app', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
'Authorization': `Bearer ${token}`
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
taskType: 'RESEARCH_TIMELINE',
|
|
55
|
+
message: topic,
|
|
56
|
+
model: 'deep'
|
|
57
|
+
})
|
|
86
58
|
});
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const errorData = await response.json();
|
|
62
|
+
throw new Error(errorData.error || 'Failed to generate timeline');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = await response.json();
|
|
87
66
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.log(chalk.
|
|
92
|
-
console.log(chalk.
|
|
67
|
+
spinner.succeed('โ Timeline generated (80 RCC)');
|
|
68
|
+
|
|
69
|
+
// Display the timeline
|
|
70
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
71
|
+
console.log(chalk.white.bold(`๐
Research Timeline: ${topic}`));
|
|
72
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
73
|
+
|
|
74
|
+
await typeAIResponse(data.response || 'Timeline data not available.');
|
|
75
|
+
|
|
76
|
+
console.log(chalk.hex('#F24E1E')('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
77
|
+
console.log(chalk.white.bold('๐ฏ YOUR RESEARCH'));
|
|
78
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
93
79
|
|
|
80
|
+
console.log(chalk.green(` โจ Your research continues from here`));
|
|
81
|
+
console.log(chalk.gray(' Add your contribution to this evolving field.\n'));
|
|
82
|
+
|
|
83
|
+
console.log(chalk.hex('#F24E1E')('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n'));
|
|
84
|
+
|
|
94
85
|
} catch (error) {
|
|
95
86
|
spinner.fail('Failed to generate timeline');
|
|
96
|
-
console.error(chalk.red(error.
|
|
87
|
+
console.error(chalk.red(error.message));
|
|
97
88
|
}
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
/**
|
|
101
|
-
* Generate sample timeline when API doesn't return structured data
|
|
102
|
-
*/
|
|
103
|
-
function generateSampleTimeline(topic) {
|
|
104
|
-
const currentYear = new Date().getFullYear();
|
|
105
|
-
|
|
106
|
-
return [
|
|
107
|
-
{
|
|
108
|
-
year: currentYear - 30,
|
|
109
|
-
title: `Foundational Research on ${topic}`,
|
|
110
|
-
description: 'Early theoretical work and initial experiments',
|
|
111
|
-
impact: 'High',
|
|
112
|
-
type: 'paper'
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
year: currentYear - 20,
|
|
116
|
-
title: 'First Major Breakthrough',
|
|
117
|
-
description: 'Key discovery that shaped the field',
|
|
118
|
-
impact: 'High',
|
|
119
|
-
type: 'breakthrough'
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
year: currentYear - 15,
|
|
123
|
-
title: 'Commercial Applications Begin',
|
|
124
|
-
description: 'First industry implementations',
|
|
125
|
-
impact: 'Medium',
|
|
126
|
-
type: 'milestone'
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
year: currentYear - 10,
|
|
130
|
-
title: 'Patent Landscape Expands',
|
|
131
|
-
description: 'Significant patent filings by major companies',
|
|
132
|
-
impact: 'Medium',
|
|
133
|
-
type: 'patent'
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
year: currentYear - 5,
|
|
137
|
-
title: 'State-of-the-Art Advances',
|
|
138
|
-
description: 'Modern techniques and methodologies',
|
|
139
|
-
impact: 'High',
|
|
140
|
-
type: 'paper'
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
year: currentYear - 2,
|
|
144
|
-
title: 'Recent Breakthrough',
|
|
145
|
-
description: 'Latest major advancement in the field',
|
|
146
|
-
impact: 'High',
|
|
147
|
-
type: 'breakthrough'
|
|
148
|
-
}
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
91
|
module.exports = { timeline };
|
package/src/firebase.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { initializeApp, getApps } = require('firebase/app');
|
|
2
2
|
const { getAuth } = require('firebase/auth');
|
|
3
3
|
const { getFirestore } = require('firebase/firestore');
|
|
4
|
+
const Conf = require('conf');
|
|
4
5
|
|
|
5
6
|
const firebaseConfig = {
|
|
6
7
|
apiKey: "AIzaSyDlgXId4pLlYqm-MDuhfz3dLH24KBRHkw8",
|
|
@@ -22,4 +23,110 @@ if (getApps().length === 0) {
|
|
|
22
23
|
const auth = getAuth(app);
|
|
23
24
|
const db = getFirestore(app);
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// Shared Conf storage for tokens
|
|
27
|
+
const tokenStore = new Conf({
|
|
28
|
+
projectName: 'omnibiofex',
|
|
29
|
+
schema: {
|
|
30
|
+
token: { type: 'string' },
|
|
31
|
+
refreshToken: { type: 'string' },
|
|
32
|
+
expiresAt: { type: 'number' }
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decode base64url to string (handles JWT format)
|
|
38
|
+
* @param {string} str - Base64url encoded string
|
|
39
|
+
* @returns {string} - Decoded string
|
|
40
|
+
*/
|
|
41
|
+
function base64UrlDecode(str) {
|
|
42
|
+
// Replace URL-safe characters with standard base64
|
|
43
|
+
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
44
|
+
// Add padding if needed
|
|
45
|
+
while (base64.length % 4) {
|
|
46
|
+
base64 += '=';
|
|
47
|
+
}
|
|
48
|
+
return Buffer.from(base64, 'base64').toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Decode JWT token to extract user info
|
|
53
|
+
* @returns {Object|null} - { uid, email, exp } or null if no token
|
|
54
|
+
*/
|
|
55
|
+
function decodeToken() {
|
|
56
|
+
const token = tokenStore.get('token');
|
|
57
|
+
if (!token) return null;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// JWT format: header.payload.signature
|
|
61
|
+
const parts = token.split('.');
|
|
62
|
+
if (parts.length !== 3) {
|
|
63
|
+
console.error('Invalid JWT format: expected 3 parts, got', parts.length);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Decode payload (base64url)
|
|
68
|
+
const payload = parts[1];
|
|
69
|
+
const decoded = base64UrlDecode(payload);
|
|
70
|
+
const data = JSON.parse(decoded);
|
|
71
|
+
|
|
72
|
+
// Firebase tokens use 'user_id' or 'sub' for UID
|
|
73
|
+
const uid = data.user_id || data.sub;
|
|
74
|
+
|
|
75
|
+
if (!uid) {
|
|
76
|
+
console.error('No user_id or sub found in token payload');
|
|
77
|
+
console.error('Token payload keys:', Object.keys(data));
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
uid: uid,
|
|
83
|
+
email: data.email,
|
|
84
|
+
exp: data.exp
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Token decode error:', error.message);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get current user UID from stored token
|
|
94
|
+
* @returns {string|null} - User UID or null
|
|
95
|
+
*/
|
|
96
|
+
function getCurrentUserUid() {
|
|
97
|
+
const decoded = decodeToken();
|
|
98
|
+
return decoded ? decoded.uid : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get current user email from stored token
|
|
103
|
+
* @returns {string|null} - User email or null
|
|
104
|
+
*/
|
|
105
|
+
function getCurrentUserEmail() {
|
|
106
|
+
const decoded = decodeToken();
|
|
107
|
+
return decoded ? decoded.email : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if token is valid (not expired)
|
|
112
|
+
* @returns {boolean}
|
|
113
|
+
*/
|
|
114
|
+
function isTokenValid() {
|
|
115
|
+
const decoded = decodeToken();
|
|
116
|
+
if (!decoded || !decoded.exp) return false;
|
|
117
|
+
|
|
118
|
+
// JWT exp is in seconds, Date.now() is in milliseconds
|
|
119
|
+
const now = Math.floor(Date.now() / 1000);
|
|
120
|
+
return decoded.exp > now;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
app,
|
|
125
|
+
auth,
|
|
126
|
+
db,
|
|
127
|
+
tokenStore,
|
|
128
|
+
decodeToken,
|
|
129
|
+
getCurrentUserUid,
|
|
130
|
+
getCurrentUserEmail,
|
|
131
|
+
isTokenValid
|
|
132
|
+
};
|