aether-hub 1.0.3
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/LICENSE +21 -0
- package/README.md +110 -0
- package/commands/doctor.js +720 -0
- package/commands/init.js +685 -0
- package/commands/logs.js +315 -0
- package/commands/monitor.js +431 -0
- package/commands/sdk.js +381 -0
- package/commands/validator-start.js +290 -0
- package/commands/validator-status.js +268 -0
- package/index.js +275 -0
- package/package.json +51 -0
- package/test/doctor.test.js +76 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aether-cli validator-status
|
|
3
|
+
*
|
|
4
|
+
* Queries the validator's RPC endpoint and displays status information.
|
|
5
|
+
* Shows slot height, peer count, block production, and epoch info.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const http = require('http');
|
|
9
|
+
|
|
10
|
+
// ANSI colors
|
|
11
|
+
const colors = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bright: '\x1b[1m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
cyan: '\x1b[36m',
|
|
17
|
+
red: '\x1b[31m',
|
|
18
|
+
dim: '\x1b[2m',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make an RPC call to the validator
|
|
23
|
+
*/
|
|
24
|
+
function rpcCall(url, method, params = []) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const urlObj = new URL(url);
|
|
27
|
+
|
|
28
|
+
const postData = JSON.stringify({
|
|
29
|
+
jsonrpc: '2.0',
|
|
30
|
+
id: 1,
|
|
31
|
+
method,
|
|
32
|
+
params,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const options = {
|
|
36
|
+
hostname: urlObj.hostname,
|
|
37
|
+
port: urlObj.port || 8899,
|
|
38
|
+
path: '/',
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const req = http.request(options, (res) => {
|
|
47
|
+
let data = '';
|
|
48
|
+
res.on('data', (chunk) => data += chunk);
|
|
49
|
+
res.on('end', () => {
|
|
50
|
+
try {
|
|
51
|
+
const json = JSON.parse(data);
|
|
52
|
+
if (json.error) {
|
|
53
|
+
reject(new Error(json.error.message || JSON.stringify(json.error)));
|
|
54
|
+
} else {
|
|
55
|
+
resolve(json.result);
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
reject(new Error(`Invalid JSON response: ${data}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
req.on('error', (e) => {
|
|
64
|
+
reject(new Error(`Connection failed: ${e.message}`));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
req.write(postData);
|
|
68
|
+
req.end();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse command line args
|
|
74
|
+
*/
|
|
75
|
+
function parseArgs() {
|
|
76
|
+
const args = process.argv.slice(3); // Skip 'aether-cli validator status'
|
|
77
|
+
|
|
78
|
+
const options = {
|
|
79
|
+
rpcUrl: 'http://127.0.0.1:8899',
|
|
80
|
+
details: false,
|
|
81
|
+
json: false,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
switch (args[i]) {
|
|
86
|
+
case '--rpc-url':
|
|
87
|
+
options.rpcUrl = args[++i];
|
|
88
|
+
break;
|
|
89
|
+
case '-v':
|
|
90
|
+
case '--verbose':
|
|
91
|
+
case '--details':
|
|
92
|
+
options.details = true;
|
|
93
|
+
break;
|
|
94
|
+
case '--json':
|
|
95
|
+
options.json = true;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return options;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Print the status display
|
|
105
|
+
*/
|
|
106
|
+
function printStatus(status, options) {
|
|
107
|
+
const epochProgress = options.slotsInEpoch > 0
|
|
108
|
+
? ((options.slotIndex / options.slotsInEpoch) * 100).toFixed(1)
|
|
109
|
+
: '0.0';
|
|
110
|
+
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(`${colors.cyan}╔═══════════════════════════════════════════════════════════════╗`);
|
|
113
|
+
console.log(`${colors.cyan}║ ║`);
|
|
114
|
+
console.log(`${colors.cyan}║ ${colors.bright}AETHER VALIDATOR STATUS${colors.reset}${colors.cyan} ║`);
|
|
115
|
+
console.log(`${colors.cyan}║ ║`);
|
|
116
|
+
console.log(`${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
117
|
+
console.log();
|
|
118
|
+
|
|
119
|
+
console.log(` ${colors.bright}🌐 RPC Endpoint:${colors.reset} ${options.rpcUrl}`);
|
|
120
|
+
console.log();
|
|
121
|
+
|
|
122
|
+
// Check if connected
|
|
123
|
+
if (status.error) {
|
|
124
|
+
console.log(` ${colors.red}❌ Error:${colors.reset} ${status.error}`);
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(` ${colors.yellow}Make sure the validator is running:${colors.reset}`);
|
|
127
|
+
console.log(` ${colors.bright}aether-cli validator start${colors.reset}`);
|
|
128
|
+
console.log();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(` ${colors.green}✅ Connected${colors.reset}`);
|
|
133
|
+
console.log();
|
|
134
|
+
|
|
135
|
+
console.log(` ${colors.bright}📊 Chain Status${colors.reset}`);
|
|
136
|
+
console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
|
|
137
|
+
console.log(` Slot Height: ${colors.bright}${String(status.slot || 0).padStart(12)}${colors.reset}`);
|
|
138
|
+
console.log(` Block Height: ${colors.bright}${String(status.blockHeight || 0).padStart(12)}${colors.reset}`);
|
|
139
|
+
console.log(` Transaction Count: ${colors.bright}${String(status.transactionCount || 0).padStart(12)}${colors.reset}`);
|
|
140
|
+
console.log();
|
|
141
|
+
|
|
142
|
+
console.log(` ${colors.bright}🔗 Network${colors.reset}`);
|
|
143
|
+
console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
|
|
144
|
+
console.log(` Peer Count: ${colors.bright}${String(status.peerCount || 0).padStart(12)}${colors.reset}`);
|
|
145
|
+
console.log();
|
|
146
|
+
|
|
147
|
+
console.log(` ${colors.bright}📈 Epoch ${status.epoch || 0}${colors.reset}`);
|
|
148
|
+
console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
|
|
149
|
+
console.log(` Progress: ${colors.bright}${String(epochProgress + '%').padStart(12)}${colors.reset}`);
|
|
150
|
+
console.log(` Slot Index: ${colors.bright}${String(options.slotIndex || 0).padStart(12)}${colors.reset}`);
|
|
151
|
+
console.log(` Slots in Epoch: ${colors.bright}${String(options.slotsInEpoch || 0).padStart(12)}${colors.reset}`);
|
|
152
|
+
console.log();
|
|
153
|
+
|
|
154
|
+
if (options.details && status.blockProduction) {
|
|
155
|
+
const bp = status.blockProduction;
|
|
156
|
+
console.log(` ${colors.bright}📦 Block Production${colors.reset}`);
|
|
157
|
+
console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
|
|
158
|
+
console.log(` Blocks Produced: ${colors.bright}${String(bp.blocksProduced || 0).padStart(12)}${colors.reset}`);
|
|
159
|
+
console.log(` Entries Produced: ${colors.bright}${String(bp.entriesProduced || 0).padStart(12)}${colors.reset}`);
|
|
160
|
+
console.log();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(` ${colors.green}✓ Validator is running normally${colors.reset}`);
|
|
164
|
+
console.log();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Main status command
|
|
169
|
+
*/
|
|
170
|
+
async function validatorStatus() {
|
|
171
|
+
const options = parseArgs();
|
|
172
|
+
|
|
173
|
+
let status = {
|
|
174
|
+
slot: 0,
|
|
175
|
+
blockHeight: 0,
|
|
176
|
+
transactionCount: 0,
|
|
177
|
+
peerCount: 0,
|
|
178
|
+
epoch: 0,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let epochInfo = {};
|
|
182
|
+
let blockProduction = {};
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Make parallel RPC calls
|
|
186
|
+
const [slot, blockHeight, transactionCount, epochInfoResult, blockProdResult] = await Promise.all([
|
|
187
|
+
rpcCall(options.rpcUrl, 'getSlot').catch(e => ({ error: e.message })),
|
|
188
|
+
rpcCall(options.rpcUrl, 'getBlockHeight').catch(e => ({ error: e.message })),
|
|
189
|
+
rpcCall(options.rpcUrl, 'getTransactionCount').catch(e => ({ error: e.message })),
|
|
190
|
+
rpcCall(options.rpcUrl, 'getEpochInfo').catch(e => ({})),
|
|
191
|
+
options.details ? rpcCall(options.rpcUrl, 'getBlockProduction').catch(e => ({})) : Promise.resolve({}),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
if (typeof slot === 'object' && slot.error) {
|
|
195
|
+
if (options.json) {
|
|
196
|
+
console.log(JSON.stringify({ error: slot.error }, null, 2));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
console.log();
|
|
200
|
+
console.log(` ${colors.red}❌ Cannot connect to validator${colors.reset}`);
|
|
201
|
+
console.log(` ${colors.yellow}${slot.error}${colors.reset}`);
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(` ${colors.bright}Start the validator first:${colors.reset}`);
|
|
204
|
+
console.log(` ${colors.cyan}aether-cli validator start${colors.reset}`);
|
|
205
|
+
console.log();
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
status.slot = typeof slot === 'number' ? slot : 0;
|
|
210
|
+
status.blockHeight = typeof blockHeight === 'number' ? blockHeight : status.slot;
|
|
211
|
+
status.transactionCount = typeof transactionCount === 'number' ? transactionCount : 0;
|
|
212
|
+
|
|
213
|
+
if (epochInfoResult && typeof epochInfoResult === 'object') {
|
|
214
|
+
epochInfo = epochInfoResult;
|
|
215
|
+
status.epoch = epochInfo.epoch || 0;
|
|
216
|
+
epochInfo.slotIndex = epochInfo.slotIndex || 0;
|
|
217
|
+
epochInfo.slotsInEpoch = epochInfo.slotsInEpoch || 432000;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (blockProdResult && typeof blockProdResult === 'object') {
|
|
221
|
+
blockProduction = blockProdResult;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Get peer count
|
|
225
|
+
try {
|
|
226
|
+
status.peerCount = await rpcCall(options.rpcUrl, 'getPeerCount') || 0;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
status.peerCount = 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (options.json) {
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
slot: status.slot,
|
|
234
|
+
blockHeight: status.blockHeight,
|
|
235
|
+
transactionCount: status.transactionCount,
|
|
236
|
+
peerCount: status.peerCount,
|
|
237
|
+
epoch: status.epoch,
|
|
238
|
+
epochInfo,
|
|
239
|
+
blockProduction,
|
|
240
|
+
}, null, 2));
|
|
241
|
+
} else {
|
|
242
|
+
printStatus(status, {
|
|
243
|
+
...options,
|
|
244
|
+
...epochInfo,
|
|
245
|
+
blockProduction,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (options.json) {
|
|
251
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
252
|
+
} else {
|
|
253
|
+
console.log();
|
|
254
|
+
console.log(` ${colors.red}❌ Error querying validator${colors.reset}`);
|
|
255
|
+
console.log(` ${colors.yellow}${err.message}${colors.reset}`);
|
|
256
|
+
console.log();
|
|
257
|
+
}
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Export for use as module
|
|
263
|
+
module.exports = { validatorStatus, rpcCall };
|
|
264
|
+
|
|
265
|
+
// Run if called directly
|
|
266
|
+
if (require.main === module) {
|
|
267
|
+
validatorStatus();
|
|
268
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli - AeTHer Validator Command Line Interface
|
|
4
|
+
*
|
|
5
|
+
* Main entry point for the validator CLI tool.
|
|
6
|
+
* Provides onboarding, system checks, validator management, and KYC integration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { doctorCommand } = require('./commands/doctor');
|
|
10
|
+
const { validatorStart } = require('./commands/validator-start');
|
|
11
|
+
const { validatorStatus } = require('./commands/validator-status');
|
|
12
|
+
const { init } = require('./commands/init');
|
|
13
|
+
const { monitorLoop } = require('./commands/monitor');
|
|
14
|
+
const { logsCommand } = require('./commands/logs');
|
|
15
|
+
const { sdkCommand } = require('./commands/sdk');
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
|
|
18
|
+
// CLI version
|
|
19
|
+
const VERSION = '1.0.2';
|
|
20
|
+
|
|
21
|
+
// Parse args early to support flags on commands
|
|
22
|
+
function getCommandArgs() {
|
|
23
|
+
return process.argv.slice(2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Tier colours
|
|
27
|
+
const TIER_COLORS = {
|
|
28
|
+
FULL: '\x1b[36m', // cyan
|
|
29
|
+
LITE: '\x1b[33m', // yellow
|
|
30
|
+
OBSERVER: '\x1b[32m', // green
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Display the interactive main menu
|
|
36
|
+
*/
|
|
37
|
+
async function showMenu() {
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const prompt = (q) => new Promise((res) => rl.question(q, res));
|
|
44
|
+
|
|
45
|
+
console.log(
|
|
46
|
+
TIER_COLORS.FULL + '\n ╔═══════════════════════════════════════════════╗\n' +
|
|
47
|
+
' ║ AETHER CHAIN — Validator Setup Wizard ║\n' +
|
|
48
|
+
' ╚═══════════════════════════════════════════════╝' + TIER_COLORS.reset + '\n'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
console.log(' Welcome to AeTHer Chain. What would you like to do?\n');
|
|
52
|
+
console.log(' ' + TIER_COLORS.FULL + '1)' + TIER_COLORS.reset + ' 🩺 Doctor — Check if your system meets requirements');
|
|
53
|
+
console.log(' ' + TIER_COLORS.FULL + '2)' + TIER_COLORS.reset + ' 🚀 Start — Begin validator onboarding (recommended)');
|
|
54
|
+
console.log(' ' + TIER_COLORS.FULL + '3)' + TIER_COLORS.reset + ' 📊 Monitor — Watch live validator stats');
|
|
55
|
+
console.log(' ' + TIER_COLORS.FULL + '4)' + TIER_COLORS.reset + ' 📋 Logs — Tail and colourise validator logs');
|
|
56
|
+
console.log(' ' + TIER_COLORS.FULL + '5)' + TIER_COLORS.reset + ' 📦 SDK — Get SDK links and install tools');
|
|
57
|
+
console.log(' ' + TIER_COLORS.FULL + '6)' + TIER_COLORS.reset + ' ❓ Help — Show all commands\n');
|
|
58
|
+
console.log(' ' + TIER_COLORS.reset + ' Type a number or command name. Press Ctrl+C to exit.\n');
|
|
59
|
+
|
|
60
|
+
const VALID_CHOICES = ['1', '2', '3', '4', '5', '6', 'doctor', 'init', 'monitor', 'logs', 'sdk', 'help'];
|
|
61
|
+
|
|
62
|
+
while (true) {
|
|
63
|
+
const answer = (await prompt(` > `)).trim().toLowerCase();
|
|
64
|
+
|
|
65
|
+
if (answer === '' || answer === '1' || answer === 'doctor') {
|
|
66
|
+
rl.close();
|
|
67
|
+
const { doctorCommand } = require('./commands/doctor');
|
|
68
|
+
doctorCommand({ autoFix: false, tier: 'full' });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (answer === '2' || answer === 'init' || answer === 'start') {
|
|
73
|
+
rl.close();
|
|
74
|
+
const { init } = require('./commands/init');
|
|
75
|
+
init();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (answer === '3' || answer === 'monitor') {
|
|
80
|
+
rl.close();
|
|
81
|
+
const { main } = require('./commands/monitor');
|
|
82
|
+
main();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (answer === '4' || answer === 'logs') {
|
|
87
|
+
rl.close();
|
|
88
|
+
const { logsCommand } = require('./commands/logs');
|
|
89
|
+
logsCommand();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (answer === '5' || answer === 'sdk') {
|
|
94
|
+
rl.close();
|
|
95
|
+
const { sdkCommand } = require('./commands/sdk');
|
|
96
|
+
sdkCommand();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (answer === '6' || answer === 'help') {
|
|
101
|
+
showHelp();
|
|
102
|
+
console.log(" Press Ctrl+C to exit or select an option above.\n");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\n ⚠️ Unknown option: "${answer}". Type 1-6 or a command name.\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Available commands
|
|
111
|
+
const COMMANDS = {
|
|
112
|
+
start: {
|
|
113
|
+
description: 'Launch interactive menu (default if no args) — same as running aether-cli with no arguments',
|
|
114
|
+
handler: () => showMenu(),
|
|
115
|
+
},
|
|
116
|
+
doctor: {
|
|
117
|
+
description: 'Run system requirements checks (CPU/RAM/Disk/Network/Firewall)',
|
|
118
|
+
handler: () => {
|
|
119
|
+
const args = getCommandArgs();
|
|
120
|
+
const autoFix = args.includes('--fix') || args.includes('-f');
|
|
121
|
+
|
|
122
|
+
// Parse --tier flag
|
|
123
|
+
let tier = 'full';
|
|
124
|
+
const tierIndex = args.findIndex(arg => arg === '--tier');
|
|
125
|
+
if (tierIndex !== -1 && args[tierIndex + 1]) {
|
|
126
|
+
tier = args[tierIndex + 1].toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
doctorCommand({ autoFix, tier });
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
init: {
|
|
133
|
+
description: 'Start onboarding wizard (generate identity, create stake account, connect to testnet)',
|
|
134
|
+
handler: init,
|
|
135
|
+
},
|
|
136
|
+
'kyc generate': {
|
|
137
|
+
description: 'Generate pre-filled KYC link with pubkey, node ID, signature',
|
|
138
|
+
handler: () => console.log('🚧 kyc generate command under development'),
|
|
139
|
+
},
|
|
140
|
+
monitor: {
|
|
141
|
+
description: 'Real-time validator dashboard (slot, block height, peers, TPS)',
|
|
142
|
+
handler: () => {
|
|
143
|
+
// monitor command runs its own loop
|
|
144
|
+
const { main } = require('./commands/monitor');
|
|
145
|
+
main();
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
logs: {
|
|
149
|
+
description: 'Tail validator logs with colour-coded output (ERROR=red, WARN=yellow, INFO=green)',
|
|
150
|
+
handler: logsCommand,
|
|
151
|
+
},
|
|
152
|
+
sdk: {
|
|
153
|
+
description: 'Aether SDK download links and install instructions (JS, Rust, FLUX/ATH tokens)',
|
|
154
|
+
handler: sdkCommand,
|
|
155
|
+
},
|
|
156
|
+
validator: {
|
|
157
|
+
description: 'Validator node management',
|
|
158
|
+
handler: () => {
|
|
159
|
+
// Handle validator subcommands
|
|
160
|
+
const subcmd = process.argv[3];
|
|
161
|
+
|
|
162
|
+
if (!subcmd) {
|
|
163
|
+
console.log('Usage: aether-cli validator <command>');
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('Commands:');
|
|
166
|
+
console.log(' start Start the validator node');
|
|
167
|
+
console.log(' status Check validator status');
|
|
168
|
+
console.log('');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
switch (subcmd) {
|
|
173
|
+
case 'start':
|
|
174
|
+
validatorStart();
|
|
175
|
+
break;
|
|
176
|
+
case 'status':
|
|
177
|
+
validatorStatus();
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
console.error(`Unknown validator command: ${subcmd}`);
|
|
181
|
+
console.error('Valid commands: start, status');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
help: {
|
|
187
|
+
description: 'Show this help message',
|
|
188
|
+
handler: showHelp,
|
|
189
|
+
},
|
|
190
|
+
version: {
|
|
191
|
+
description: 'Show version number',
|
|
192
|
+
handler: () => console.log(`aether-cli v${VERSION}`),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Display help message with ASCII art
|
|
198
|
+
*/
|
|
199
|
+
function showHelp() {
|
|
200
|
+
const header = `
|
|
201
|
+
███╗ ███╗██╗███████╗███████╗██╗ ██████╗ ███╗ ██╗
|
|
202
|
+
████╗ ████║██║██╔════╝██╔════╝██║██╔═══██╗████╗ ██║
|
|
203
|
+
██╔████╔██║██║███████╗███████╗██║██║ ██║██╔██╗ ██║
|
|
204
|
+
██║╚██╔╝██║██║╚════██║╚════██║██║██║ ██║██║╚██╗██║
|
|
205
|
+
██║ ╚═╝ ██║██║███████║███████║██║╚██████╔╝██║ ╚████║
|
|
206
|
+
╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
207
|
+
|
|
208
|
+
Validator CLI v${VERSION}
|
|
209
|
+
`.trim();
|
|
210
|
+
|
|
211
|
+
console.log(header);
|
|
212
|
+
console.log('\nUsage: aether-cli <command> [options]\n');
|
|
213
|
+
console.log('Commands:');
|
|
214
|
+
Object.entries(COMMANDS).forEach(([cmd, info]) => {
|
|
215
|
+
console.log(` ${cmd.padEnd(18)} ${info.description}`);
|
|
216
|
+
});
|
|
217
|
+
console.log('\nExamples:');
|
|
218
|
+
console.log(' aether-cli doctor # Check system requirements');
|
|
219
|
+
console.log(' aether-cli init # Start onboarding wizard');
|
|
220
|
+
console.log(' aether-cli monitor # Real-time validator dashboard');
|
|
221
|
+
console.log(' aether-cli validator start # Start validator node');
|
|
222
|
+
console.log(' aether-cli validator status # Check validator status');
|
|
223
|
+
console.log(' aether-cli --version # Show version');
|
|
224
|
+
console.log('\nDocumentation: https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop');
|
|
225
|
+
console.log('Spec: docs/MINING_VALIDATOR_TOOLS.md\n');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse command line arguments
|
|
230
|
+
*/
|
|
231
|
+
function parseArgs() {
|
|
232
|
+
const args = process.argv.slice(2);
|
|
233
|
+
|
|
234
|
+
// Handle flags
|
|
235
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
236
|
+
return 'version';
|
|
237
|
+
}
|
|
238
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
239
|
+
return 'help';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// No args → interactive menu
|
|
243
|
+
if (args.length === 0) {
|
|
244
|
+
return 'start';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Handle multi-word commands (e.g., "validator start", "kyc generate")
|
|
248
|
+
if (args.length >= 2) {
|
|
249
|
+
const multiCmd = `${args[0]} ${args[1]}`;
|
|
250
|
+
if (COMMANDS[multiCmd]) {
|
|
251
|
+
return multiCmd;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Single word command
|
|
256
|
+
return args[0] || 'help';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Main CLI entry point
|
|
261
|
+
*/
|
|
262
|
+
function main() {
|
|
263
|
+
const command = parseArgs();
|
|
264
|
+
|
|
265
|
+
if (COMMANDS[command]) {
|
|
266
|
+
COMMANDS[command].handler();
|
|
267
|
+
} else {
|
|
268
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
269
|
+
console.error('Run "aether-cli help" for usage.\n');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Run CLI
|
|
275
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aether-hub",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "AeTHer Validator CLI — tiered validators (Full/Lite/Observer), system checks, onboarding, and node management",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aether": "./index.js",
|
|
8
|
+
"aether-hub": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"commands/",
|
|
13
|
+
"test/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node test/doctor.test.js",
|
|
19
|
+
"start": "node index.js",
|
|
20
|
+
"doctor": "node index.js doctor",
|
|
21
|
+
"postinstall": "node -e \"console.log('\\n\\n [Aether] aether-hub installed!\\n\\n Run: aether start\\n Docs: https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop\\n')\""
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"aether",
|
|
25
|
+
"validator",
|
|
26
|
+
"blockchain",
|
|
27
|
+
"cli",
|
|
28
|
+
"crypto",
|
|
29
|
+
"mining"
|
|
30
|
+
],
|
|
31
|
+
"author": "Jelly-legs AI Team",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=14.0.0"
|
|
39
|
+
},
|
|
40
|
+
"os": [
|
|
41
|
+
"linux",
|
|
42
|
+
"darwin",
|
|
43
|
+
"win32"
|
|
44
|
+
],
|
|
45
|
+
"preferGlobal": true,
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"bs58": "^6.0.0",
|
|
48
|
+
"tweetnacl": "^1.0.3"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test suite for aether-cli doctor command
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { checkCPU, checkMemory, checkDisk, checkNetwork, checkFirewall } = require('../commands/doctor');
|
|
7
|
+
|
|
8
|
+
console.log('Running aether-cli doctor tests...\n');
|
|
9
|
+
|
|
10
|
+
let passed = 0;
|
|
11
|
+
let failed = 0;
|
|
12
|
+
|
|
13
|
+
function test(name, fn) {
|
|
14
|
+
try {
|
|
15
|
+
const result = fn();
|
|
16
|
+
console.log(`✅ ${name}`);
|
|
17
|
+
passed++;
|
|
18
|
+
return result;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.log(`❌ ${name}: ${error.message}`);
|
|
21
|
+
failed++;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Test CPU check
|
|
27
|
+
test('checkCPU returns valid structure', () => {
|
|
28
|
+
const cpu = checkCPU();
|
|
29
|
+
if (!cpu.section) throw new Error('Missing section');
|
|
30
|
+
if (!('passed' in cpu)) throw new Error('Missing passed');
|
|
31
|
+
if (!cpu.message) throw new Error('Missing message');
|
|
32
|
+
console.log(` CPU: ${cpu.physicalCores} cores, ${cpu.model}`);
|
|
33
|
+
return cpu;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Test Memory check
|
|
37
|
+
test('checkMemory returns valid structure', () => {
|
|
38
|
+
const mem = checkMemory();
|
|
39
|
+
if (!mem.section) throw new Error('Missing section');
|
|
40
|
+
if (!('passed' in mem)) throw new Error('Missing passed');
|
|
41
|
+
if (!mem.message) throw new Error('Missing message');
|
|
42
|
+
console.log(` Memory: ${mem.total} total, ${mem.available} available`);
|
|
43
|
+
return mem;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Test Disk check
|
|
47
|
+
test('checkDisk returns valid structure', () => {
|
|
48
|
+
const disk = checkDisk();
|
|
49
|
+
if (!disk.section) throw new Error('Missing section');
|
|
50
|
+
if (!('passed' in disk)) throw new Error('Missing passed');
|
|
51
|
+
if (!disk.message) throw new Error('Missing message');
|
|
52
|
+
console.log(` Disk: ${disk.total} total, ${disk.free} free`);
|
|
53
|
+
return disk;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Test Network check
|
|
57
|
+
test('checkNetwork returns valid structure', () => {
|
|
58
|
+
const net = checkNetwork();
|
|
59
|
+
if (!net.section) throw new Error('Missing section');
|
|
60
|
+
if (!('passed' in net)) throw new Error('Missing passed');
|
|
61
|
+
console.log(` Network: ${net.publicIP}`);
|
|
62
|
+
return net;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Test Firewall check
|
|
66
|
+
test('checkFirewall returns valid structure', () => {
|
|
67
|
+
const fw = checkFirewall();
|
|
68
|
+
if (!fw.section) throw new Error('Missing section');
|
|
69
|
+
if (!('passed' in fw)) throw new Error('Missing passed');
|
|
70
|
+
console.log(` Firewall: P2P=${fw.p2p}, RPC=${fw.rpc}, SSH=${fw.ssh}`);
|
|
71
|
+
return fw;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Summary
|
|
75
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
76
|
+
process.exit(failed > 0 ? 1 : 0);
|