decharge-scout 1.2.1 → 1.4.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/index.js +142 -39
- package/package.json +1 -1
- package/src/energy-data.js +40 -5
package/index.js
CHANGED
|
@@ -20,12 +20,13 @@ import { Command } from 'commander';
|
|
|
20
20
|
import chalk from 'chalk';
|
|
21
21
|
import ora from 'ora';
|
|
22
22
|
import dotenv from 'dotenv';
|
|
23
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
23
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
|
|
24
24
|
import { createInterface } from 'readline';
|
|
25
25
|
import path from 'path';
|
|
26
26
|
import { fileURLToPath } from 'url';
|
|
27
27
|
import { dirname } from 'path';
|
|
28
28
|
import { Keypair } from '@solana/web3.js';
|
|
29
|
+
import os from 'os';
|
|
29
30
|
|
|
30
31
|
// Load environment variables
|
|
31
32
|
dotenv.config();
|
|
@@ -68,6 +69,45 @@ function generateAgentName() {
|
|
|
68
69
|
return `Agent-${randomId}`;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Search for existing Solana wallets in common locations
|
|
74
|
+
*/
|
|
75
|
+
function findExistingWallets() {
|
|
76
|
+
const wallets = [];
|
|
77
|
+
const searchPaths = [
|
|
78
|
+
// Current directory
|
|
79
|
+
path.join(process.cwd(), 'wallet.json'),
|
|
80
|
+
path.join(process.cwd(), 'id.json'),
|
|
81
|
+
// Home directory
|
|
82
|
+
path.join(os.homedir(), '.solana', 'id.json'),
|
|
83
|
+
path.join(os.homedir(), 'wallet.json'),
|
|
84
|
+
// Common wallet names in current dir
|
|
85
|
+
path.join(process.cwd(), 'solana-wallet.json'),
|
|
86
|
+
path.join(process.cwd(), 'keypair.json'),
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const walletPath of searchPaths) {
|
|
90
|
+
if (existsSync(walletPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const keyData = JSON.parse(readFileSync(walletPath, 'utf-8'));
|
|
93
|
+
if (Array.isArray(keyData) && keyData.length === 64) {
|
|
94
|
+
const keypair = Keypair.fromSecretKey(Uint8Array.from(keyData));
|
|
95
|
+
wallets.push({
|
|
96
|
+
path: walletPath,
|
|
97
|
+
publicKey: keypair.publicKey.toBase58(),
|
|
98
|
+
name: path.basename(walletPath),
|
|
99
|
+
location: path.dirname(walletPath)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Skip invalid wallets
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return wallets;
|
|
109
|
+
}
|
|
110
|
+
|
|
71
111
|
/**
|
|
72
112
|
* Auto-create wallet if missing
|
|
73
113
|
*/
|
|
@@ -77,13 +117,72 @@ async function ensureWallet(walletPath) {
|
|
|
77
117
|
}
|
|
78
118
|
|
|
79
119
|
console.log(chalk.yellow(`\n⚠️ No wallet found at ${walletPath}`));
|
|
120
|
+
|
|
121
|
+
// Search for existing wallets
|
|
122
|
+
const existingWallets = findExistingWallets();
|
|
123
|
+
|
|
124
|
+
if (existingWallets.length > 0) {
|
|
125
|
+
console.log(chalk.green(`\n🔍 Found ${existingWallets.length} existing wallet(s):\n`));
|
|
126
|
+
|
|
127
|
+
existingWallets.forEach((wallet, index) => {
|
|
128
|
+
console.log(chalk.cyan(` ${index + 1}. ${wallet.name}`));
|
|
129
|
+
console.log(chalk.gray(` Path: ${wallet.path}`));
|
|
130
|
+
console.log(chalk.gray(` Public Key: ${wallet.publicKey}\n`));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const useExisting = await question('Use an existing wallet? (Enter number, or press Enter to create new): ');
|
|
134
|
+
|
|
135
|
+
if (useExisting.trim() && !isNaN(useExisting)) {
|
|
136
|
+
const index = parseInt(useExisting.trim()) - 1;
|
|
137
|
+
if (index >= 0 && index < existingWallets.length) {
|
|
138
|
+
const selectedWallet = existingWallets[index];
|
|
139
|
+
console.log(chalk.green(`✓ Using wallet: ${selectedWallet.publicKey}`));
|
|
140
|
+
return selectedWallet.path;
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.yellow('Invalid selection, creating new wallet...'));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
80
147
|
const answer = await question('Create a new wallet? (Y/n): ');
|
|
81
148
|
|
|
82
149
|
if (answer.toLowerCase() === 'n') {
|
|
83
|
-
console.log(chalk.blue('\
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
150
|
+
console.log(chalk.blue('\n📥 Import existing wallet'));
|
|
151
|
+
const importAnswer = await question('Do you want to import an existing wallet private key? (Y/n): ');
|
|
152
|
+
|
|
153
|
+
if (importAnswer.toLowerCase() === 'n') {
|
|
154
|
+
console.log(chalk.blue('\nYou can create a wallet manually:'));
|
|
155
|
+
console.log(chalk.gray(' solana-keygen new --outfile ./wallet.json'));
|
|
156
|
+
console.log(chalk.gray(' Or run: node setup.js\n'));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(chalk.yellow('\n⚠️ Enter your Solana wallet private key'));
|
|
161
|
+
console.log(chalk.gray('Format: [1,2,3,...] (array of 64 numbers)'));
|
|
162
|
+
const privateKeyInput = await question('Private key: ');
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Parse the private key
|
|
166
|
+
const privateKey = JSON.parse(privateKeyInput.trim());
|
|
167
|
+
|
|
168
|
+
// Validate it's an array of numbers
|
|
169
|
+
if (!Array.isArray(privateKey) || privateKey.length !== 64) {
|
|
170
|
+
throw new Error('Invalid private key format');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create keypair to validate
|
|
174
|
+
const keypair = Keypair.fromSecretKey(Uint8Array.from(privateKey));
|
|
175
|
+
|
|
176
|
+
// Save to file
|
|
177
|
+
writeFileSync(walletPath, JSON.stringify(privateKey));
|
|
178
|
+
|
|
179
|
+
console.log(chalk.green(`✓ Wallet imported: ${keypair.publicKey.toBase58()}`));
|
|
180
|
+
return walletPath;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.log(chalk.red(`\n❌ Invalid private key: ${error.message}`));
|
|
183
|
+
console.log(chalk.gray('Expected format: [1,2,3,...] (array of 64 numbers)\n'));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
87
186
|
}
|
|
88
187
|
|
|
89
188
|
console.log(chalk.blue('Generating new wallet...'));
|
|
@@ -124,7 +223,12 @@ async function ensureEnvironment() {
|
|
|
124
223
|
// Check for EIA API key
|
|
125
224
|
if (!process.env.EIA_API_KEY || process.env.EIA_API_KEY === 'your_eia_api_key_here') {
|
|
126
225
|
console.log(chalk.yellow('\n⚠️ EIA_API_KEY not configured'));
|
|
127
|
-
console.log(chalk.blue('
|
|
226
|
+
console.log(chalk.blue('\n📝 How to get a FREE EIA API key:'));
|
|
227
|
+
console.log(chalk.gray(' 1. Visit: https://www.eia.gov/opendata/register.php'));
|
|
228
|
+
console.log(chalk.gray(' 2. Fill out the registration form'));
|
|
229
|
+
console.log(chalk.gray(' 3. Check your email and verify your email address'));
|
|
230
|
+
console.log(chalk.gray(' 4. Your API key will be sent to your email'));
|
|
231
|
+
console.log(chalk.gray(' 5. Copy the API key and paste it below\n'));
|
|
128
232
|
|
|
129
233
|
const answer = await question('Enter your EIA API key (or press Enter to skip): ');
|
|
130
234
|
|
|
@@ -269,8 +373,9 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
269
373
|
let energyData;
|
|
270
374
|
|
|
271
375
|
try {
|
|
272
|
-
energyData = await fetchEnergyData();
|
|
273
|
-
|
|
376
|
+
energyData = await fetchEnergyData(location);
|
|
377
|
+
const gridRegion = energyData[0]?.source?.split('-')[1] || 'Unknown';
|
|
378
|
+
dataSpinner.succeed(chalk.green(`Fetched ${energyData.length} data points from ${energyData[0]?.source || 'EIA'}`));
|
|
274
379
|
} catch (error) {
|
|
275
380
|
dataSpinner.warn(chalk.yellow(`EIA API failed, trying Electricity Maps...`));
|
|
276
381
|
|
|
@@ -317,40 +422,38 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
317
422
|
console.log(chalk.blue('\n📊 Dashboard Data Structure:'));
|
|
318
423
|
console.log(chalk.gray(JSON.stringify(submissionData, null, 2)));
|
|
319
424
|
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
425
|
+
// Submit to DeCharge Scout dashboard
|
|
426
|
+
try {
|
|
427
|
+
const dashboardSpinner = ora('Submitting to DeCharge Scout dashboard...').start();
|
|
428
|
+
const apiUrl = 'https://decharge-scout.vercel.app/api/agentone/submit';
|
|
429
|
+
|
|
430
|
+
console.log(chalk.blue(`\n🌐 Dashboard API URL: ${apiUrl}`));
|
|
431
|
+
console.log(chalk.gray(`📤 Submitting data...`));
|
|
432
|
+
|
|
433
|
+
const response = await fetch(apiUrl, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: { 'Content-Type': 'application/json' },
|
|
436
|
+
body: JSON.stringify({
|
|
437
|
+
...submissionData,
|
|
438
|
+
wallet: wallet.publicKey.toBase58(),
|
|
439
|
+
run_number: totalRuns
|
|
440
|
+
})
|
|
441
|
+
});
|
|
325
442
|
|
|
326
|
-
|
|
327
|
-
|
|
443
|
+
const responseText = await response.text();
|
|
444
|
+
console.log(chalk.blue(`📥 API Response Status: ${response.status}`));
|
|
328
445
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log(chalk.blue(`📥 API Response Status: ${response.status}`));
|
|
337
|
-
console.log(chalk.gray(`📥 API Response Body: ${responseText}`));
|
|
338
|
-
|
|
339
|
-
if (response.ok) {
|
|
340
|
-
dashboardSpinner.succeed(chalk.green('✅ Dashboard submission successful!'));
|
|
341
|
-
console.log(chalk.green(`🎉 Data should now appear at: https://decharge-scout.vercel.app/agentone`));
|
|
342
|
-
} else {
|
|
343
|
-
dashboardSpinner.warn(chalk.yellow(`⚠️ Dashboard API returned: ${response.status}`));
|
|
344
|
-
console.log(chalk.yellow(`Response: ${responseText}`));
|
|
345
|
-
}
|
|
346
|
-
} catch (error) {
|
|
347
|
-
// Silent fail for dashboard - it's optional
|
|
348
|
-
console.log(chalk.red(`❌ Dashboard API error: ${error.message}`));
|
|
349
|
-
console.log(chalk.gray(`Stack: ${error.stack}`));
|
|
446
|
+
if (response.ok) {
|
|
447
|
+
dashboardSpinner.succeed(chalk.green('✅ Dashboard submission successful!'));
|
|
448
|
+
console.log(chalk.green(`🎉 Data should now appear at: https://decharge-scout.vercel.app/agentone`));
|
|
449
|
+
console.log(chalk.gray(`Response: ${responseText}`));
|
|
450
|
+
} else {
|
|
451
|
+
dashboardSpinner.warn(chalk.yellow(`⚠️ Dashboard API returned: ${response.status}`));
|
|
452
|
+
console.log(chalk.yellow(`Response: ${responseText}`));
|
|
350
453
|
}
|
|
351
|
-
}
|
|
352
|
-
console.log(chalk.
|
|
353
|
-
console.log(chalk.gray(`
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.log(chalk.red(`❌ Dashboard submission failed: ${error.message}`));
|
|
456
|
+
console.log(chalk.gray(`This won't affect your oracle submission or points`));
|
|
354
457
|
}
|
|
355
458
|
|
|
356
459
|
// Award points
|
package/package.json
CHANGED
package/src/energy-data.js
CHANGED
|
@@ -13,15 +13,48 @@ const EIA_API_KEY = process.env.EIA_API_KEY;
|
|
|
13
13
|
const EIA_BASE_URL = 'https://api.eia.gov/v2';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Map location to EIA grid region codes
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
function getGridRegionForLocation(location) {
|
|
19
|
+
const locationLower = location.toLowerCase();
|
|
20
|
+
|
|
21
|
+
// Map states/regions to grid operators
|
|
22
|
+
if (locationLower.includes('texas') || locationLower.includes('tx') || locationLower.includes('dallas') || locationLower.includes('houston') || locationLower.includes('austin')) {
|
|
23
|
+
return 'ERCT'; // ERCOT (Texas)
|
|
24
|
+
} else if (locationLower.includes('california') || locationLower.includes('ca')) {
|
|
25
|
+
return 'CISO'; // California ISO
|
|
26
|
+
} else if (locationLower.includes('new york') || locationLower.includes('ny')) {
|
|
27
|
+
return 'NYIS'; // New York ISO
|
|
28
|
+
} else if (locationLower.includes('new england') || locationLower.includes('massachusetts') || locationLower.includes('ma')) {
|
|
29
|
+
return 'ISNE'; // ISO New England
|
|
30
|
+
} else if (locationLower.includes('pjm') || locationLower.includes('pennsylvania') || locationLower.includes('pa')) {
|
|
31
|
+
return 'PJM'; // PJM Interconnection
|
|
32
|
+
} else if (locationLower.includes('miso') || locationLower.includes('midwest')) {
|
|
33
|
+
return 'MISO'; // Midcontinent ISO
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Default to ERCOT if unknown
|
|
37
|
+
return 'ERCT';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Fetch energy data from EIA API
|
|
42
|
+
*/
|
|
43
|
+
export async function fetchEnergyData(location = 'Texas, USA') {
|
|
19
44
|
if (!EIA_API_KEY || EIA_API_KEY === 'your_eia_api_key_here' || EIA_API_KEY.length < 20) {
|
|
20
45
|
console.warn('⚠️ EIA_API_KEY not configured or invalid');
|
|
21
46
|
console.warn(' Get a free key: https://www.eia.gov/opendata/register.php');
|
|
22
47
|
throw new Error('EIA_API_KEY not configured');
|
|
23
48
|
}
|
|
24
49
|
|
|
50
|
+
// Debug logging (show first 6 and last 4 chars of API key)
|
|
51
|
+
const keyPreview = `${EIA_API_KEY.substring(0, 6)}...${EIA_API_KEY.substring(EIA_API_KEY.length - 4)}`;
|
|
52
|
+
console.log(`📡 Using EIA API key: ${keyPreview}`);
|
|
53
|
+
|
|
54
|
+
// Determine grid region from location
|
|
55
|
+
const gridRegion = getGridRegionForLocation(location);
|
|
56
|
+
console.log(`🗺️ Location: ${location} → Grid Region: ${gridRegion}`);
|
|
57
|
+
|
|
25
58
|
try {
|
|
26
59
|
// Calculate time range (last 24 hours to next 24 hours for forecast)
|
|
27
60
|
const now = new Date();
|
|
@@ -29,12 +62,12 @@ export async function fetchEnergyData() {
|
|
|
29
62
|
const startDate = yesterday.toISOString().split('T')[0] + 'T00';
|
|
30
63
|
const endDate = now.toISOString().split('T')[0] + 'T23';
|
|
31
64
|
|
|
32
|
-
// EIA API endpoint for
|
|
65
|
+
// EIA API endpoint for regional real-time data
|
|
33
66
|
const url = `${EIA_BASE_URL}/electricity/rto/region-data/data/?` +
|
|
34
67
|
`api_key=${EIA_API_KEY}` +
|
|
35
68
|
`&frequency=hourly` +
|
|
36
69
|
`&data[0]=value` +
|
|
37
|
-
`&facets[respondent][]
|
|
70
|
+
`&facets[respondent][]=${gridRegion}` +
|
|
38
71
|
`&facets[type][]=D` + // Demand
|
|
39
72
|
`&start=${startDate}` +
|
|
40
73
|
`&end=${endDate}` +
|
|
@@ -42,6 +75,8 @@ export async function fetchEnergyData() {
|
|
|
42
75
|
`&sort[0][direction]=desc` +
|
|
43
76
|
`&length=48`;
|
|
44
77
|
|
|
78
|
+
console.log(`🔗 EIA API URL: ${url.replace(EIA_API_KEY, keyPreview)}`);
|
|
79
|
+
|
|
45
80
|
const response = await fetch(url, {
|
|
46
81
|
headers: {
|
|
47
82
|
'Accept': 'application/json'
|
|
@@ -74,7 +109,7 @@ export async function fetchEnergyData() {
|
|
|
74
109
|
hour: new Date(item.period).getHours(),
|
|
75
110
|
demand: demand,
|
|
76
111
|
price: Math.max(0.03, Math.min(0.15, price)), // Clamp between 3-15 cents
|
|
77
|
-
source:
|
|
112
|
+
source: `EIA-${gridRegion}`
|
|
78
113
|
};
|
|
79
114
|
});
|
|
80
115
|
|