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.
@@ -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);