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 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('\nYou can create a wallet manually:'));
84
- console.log(chalk.gray(' solana-keygen new --outfile ./wallet.json'));
85
- console.log(chalk.gray(' Or run: node setup.js\n'));
86
- process.exit(1);
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('Get a free API key from: https://www.eia.gov/opendata/register.php'));
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
- dataSpinner.succeed(chalk.green(`Fetched ${energyData.length} data points from EIA (ERCOT)`));
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
- // Optional: Submit to mock dashboard API
321
- if (process.env.DASHBOARD_API_URL) {
322
- try {
323
- const dashboardSpinner = ora('Submitting to dashboard API...').start();
324
- const apiUrl = process.env.DASHBOARD_API_URL;
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
- console.log(chalk.blue(`\n🌐 Dashboard API URL: ${apiUrl}`));
327
- console.log(chalk.gray(`📤 Submitting data: ${JSON.stringify(submissionData, null, 2)}`));
443
+ const responseText = await response.text();
444
+ console.log(chalk.blue(`📥 API Response Status: ${response.status}`));
328
445
 
329
- const response = await fetch(apiUrl, {
330
- method: 'POST',
331
- headers: { 'Content-Type': 'application/json' },
332
- body: JSON.stringify(submissionData)
333
- });
334
-
335
- const responseText = await response.text();
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
- } else {
352
- console.log(chalk.yellow(`\n⚠️ DASHBOARD_API_URL not set - skipping dashboard submission`));
353
- console.log(chalk.gray(` To enable: Set DASHBOARD_API_URL in .env file`));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decharge-scout",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "AI-powered energy grid data scout with Solana integration",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
- * Fetch energy data from EIA API (ERCOT)
16
+ * Map location to EIA grid region codes
17
17
  */
18
- export async function fetchEnergyData() {
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 ERCOT real-time data
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][]=ERCT` +
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: 'EIA-ERCOT'
112
+ source: `EIA-${gridRegion}`
78
113
  };
79
114
  });
80
115