@jellylegsai/aether-cli 1.9.2 → 2.0.1

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.
@@ -1,429 +1,412 @@
1
- #!/usr/bin/env node
2
- /**
3
- * aether-cli network - Aether Network Status
4
- *
5
- * Queries the Aether network for broad health metrics:
6
- * - Network-wide slot, block height, slot production
7
- * - Connected peers and their info
8
- * - Consensus status and epoch info
9
- * - TPS estimates from the network
10
- *
11
- * Usage:
12
- * aether-cli network # Interactive summary view
13
- * aether-cli network --json # JSON output for scripting
14
- * aether-cli network --rpc <url> # Query a specific RPC endpoint
15
- * aether-cli network --peers # Detailed peer list
16
- * aether-cli network --epoch # Current epoch and consensus info
17
- *
18
- * Uses @jellylegsai/aether-sdk for real blockchain RPC calls to http://127.0.0.1:8899
19
- */
20
-
21
- // ANSI colours
22
- const C = {
23
- reset: '\x1b[0m',
24
- bright: '\x1b[1m',
25
- dim: '\x1b[2m',
26
- red: '\x1b[31m',
27
- green: '\x1b[32m',
28
- yellow: '\x1b[33m',
29
- blue: '\x1b[34m',
30
- cyan: '\x1b[36m',
31
- magenta: '\x1b[35m',
32
- white: '\x1b[37m',
33
- };
34
-
35
- // Import SDK for real blockchain RPC calls
36
- const path = require('path');
37
- const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
38
- const aether = require(sdkPath);
39
-
40
- const DEFAULT_RPC = process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
41
-
42
- // ---------------------------------------------------------------------------
43
- // Argument parsing
44
- // ---------------------------------------------------------------------------
45
-
46
- function parseArgs() {
47
- const args = process.argv.slice(2);
48
- const options = {
49
- rpc: DEFAULT_RPC,
50
- showPeers: false,
51
- showEpoch: false,
52
- asJson: false,
53
- };
54
-
55
- for (let i = 0; i < args.length; i++) {
56
- if (args[i] === '--rpc' || args[i] === '-r') {
57
- options.rpc = args[++i];
58
- } else if (args[i] === '--peers' || args[i] === '-p') {
59
- options.showPeers = true;
60
- } else if (args[i] === '--epoch' || args[i] === '-e') {
61
- options.showEpoch = true;
62
- } else if (args[i] === '--json' || args[i] === '-j') {
63
- options.asJson = true;
64
- } else if (args[i] === '--help' || args[i] === '-h') {
65
- showHelp();
66
- process.exit(0);
67
- }
68
- }
69
-
70
- return options;
71
- }
72
-
73
- function showHelp() {
74
- console.log(`
75
- ${C.bright}${C.cyan}aether-cli network${C.reset} - Aether Network Status
76
-
77
- ${C.bright}Usage:${C.reset}
78
- aether-cli network [options]
79
-
80
- ${C.bright}Options:${C.reset}
81
- -r, --rpc <url> RPC endpoint (default: ${DEFAULT_RPC} or $AETHER_RPC)
82
- -p, --peers Show detailed peer list
83
- -e, --epoch Show epoch and consensus information
84
- -j, --json Output raw JSON (good for scripting)
85
- -h, --help Show this help message
86
-
87
- ${C.bright}Examples:${C.reset}
88
- aether-cli network # Summary view
89
- aether-cli network --json # JSON output
90
- aether-cli network --rpc http://api.testnet.aether.network
91
- aether-cli network --peers # Detailed peer list
92
- aether-cli network --epoch # Epoch/consensus info
93
- `.trim());
94
- }
95
-
96
- // ---------------------------------------------------------------------------
97
- // Network data fetchers using SDK (real blockchain RPC calls)
98
- // ---------------------------------------------------------------------------
99
-
100
- /** Create SDK client for custom RPC */
101
- function createClient(rpc) {
102
- return new aether.AetherClient({ rpcUrl: rpc });
103
- }
104
-
105
- /** GET /v1/slot — current network slot (via SDK) */
106
- async function getSlot(rpc) {
107
- try {
108
- const client = createClient(rpc);
109
- return await client.getSlot();
110
- } catch {
111
- return null;
112
- }
113
- }
114
-
115
- /** GET /v1/blockheight current network block height (via SDK) */
116
- async function getBlockHeight(rpc) {
117
- try {
118
- const client = createClient(rpc);
119
- return await client.getBlockHeight();
120
- } catch {
121
- return null;
122
- }
123
- }
124
-
125
- /** GET /v1/validators list of validators / peers (via SDK) */
126
- async function getValidators(rpc) {
127
- try {
128
- const client = createClient(rpc);
129
- return await client.getValidators();
130
- } catch {
131
- return [];
132
- }
133
- }
134
-
135
- /** GET /v1/epoch — current epoch and consensus info (via SDK) */
136
- async function getEpoch(rpc) {
137
- try {
138
- const client = createClient(rpc);
139
- return await client.getEpochInfo();
140
- } catch {
141
- return null;
142
- }
143
- }
144
-
145
- /** POST /v1/slot_production — slot production stats (via SDK) */
146
- async function getSlotProduction(rpc) {
147
- try {
148
- const client = createClient(rpc);
149
- return await client.getSlotProduction();
150
- } catch {
151
- return null;
152
- }
153
- }
154
-
155
- /** GET /v1/tps — TPS estimate from network (via SDK) */
156
- async function getTPS(rpc) {
157
- try {
158
- const client = createClient(rpc);
159
- return await client.getTPS();
160
- } catch {
161
- return null;
162
- }
163
- }
164
-
165
- /** GET /v1/supply — token supply info (via SDK) */
166
- async function getSupply(rpc) {
167
- try {
168
- const client = createClient(rpc);
169
- return await client.getSupply();
170
- } catch {
171
- return null;
172
- }
173
- }
174
-
175
- // ---------------------------------------------------------------------------
176
- // Output formatters
177
- // ---------------------------------------------------------------------------
178
-
179
- function formatNumber(n) {
180
- if (n === null || n === undefined) return 'N/A';
181
- return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
182
- }
183
-
184
- function peerHealthIcon(score) {
185
- if (score === undefined || score === null) return `${C.dim}●${C.reset}`;
186
- if (score >= 80) return `${C.green}●${C.reset}`;
187
- if (score >= 50) return `${C.yellow}●${C.reset}`;
188
- return `${C.red}●${C.reset}`;
189
- }
190
-
191
- function uptimeString(seconds) {
192
- if (!seconds) return `${C.dim}unknown${C.reset}`;
193
- const d = Math.floor(seconds / 86400);
194
- const h = Math.floor((seconds % 86400) / 3600);
195
- const m = Math.floor((seconds % 3600) / 60);
196
- const parts = [];
197
- if (d > 0) parts.push(`${d}d`);
198
- if (h > 0) parts.push(`${h}h`);
199
- if (m > 0) parts.push(`${m}m`);
200
- return parts.length > 0 ? parts.join(' ') : `${seconds}s`;
201
- }
202
-
203
- // ---------------------------------------------------------------------------
204
- // Main renderers
205
- // ---------------------------------------------------------------------------
206
-
207
- function renderSummary(data, rpc) {
208
- const { slot, blockHeight, peerCount, tps, supply, epochData } = data;
209
- const now = new Date().toLocaleTimeString();
210
-
211
- console.log(`
212
- ${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}
213
- ${C.bright}${C.cyan}║${C.reset} ${C.bright}AETHER NETWORK STATUS${C.reset}${C.cyan} ║${C.reset}
214
- ${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}
215
- `);
216
- console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
217
- console.log(` ${C.dim}Updated:${C.reset} ${now}`);
218
- console.log();
219
-
220
- // Network health
221
- const isHealthy = slot !== null && blockHeight !== null;
222
- const healthIcon = isHealthy ? `${C.green}● HEALTHY${C.reset}` : `${C.red}● UNHEALTHY${C.reset}`;
223
- console.log(` Network ${healthIcon}`);
224
- console.log();
225
-
226
- // Key metrics
227
- console.log(` ${C.bright}┌─────────────────────────────────────────────────────────────────┐${C.reset}`);
228
- console.log(` ${C.bright}│${C.reset} ${C.cyan}Current Slot${C.reset} ${C.bright}│${C.reset} ${C.green}${formatNumber(slot).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
229
- console.log(` ${C.bright}│${C.reset} ${C.cyan}Block Height${C.reset} ${C.bright}│${C.reset} ${C.blue}${formatNumber(blockHeight).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
230
- console.log(` ${C.bright}│${C.reset} ${C.cyan}Active Peers${C.reset} ${C.bright}│${C.reset} ${C.magenta}${formatNumber(peerCount).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
231
- console.log(` ${C.bright}│${C.reset} ${C.cyan}Network TPS${C.reset} ${C.bright}│${C.reset} ${tps !== null ? (tps > 0 ? `${C.green}` : `${C.yellow}`) + tps.toFixed(2).padEnd(20) : `${C.dim}N/A`.padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
232
- console.log(` ${C.bright}└─────────────────────────────────────────────────────────────────┘${C.reset}`);
233
- console.log();
234
-
235
- // Epoch info if available
236
- if (epochData && (epochData.epoch !== undefined || epochData.absolute_slot !== undefined)) {
237
- console.log(` ${C.bright}── Epoch / Consensus ──────────────────────────────────────────${C.reset}`);
238
- const ep = epochData.epoch !== undefined ? epochData.epoch : '?';
239
- const slotIndex = epochData.slot_index !== undefined ? epochData.slot_index : '?';
240
- const slotsInEpoch = epochData.slots_in_epoch !== undefined ? epochData.slots_in_epoch : '?';
241
- const progress = slotsInEpoch !== '?' && slotsInEpoch > 0
242
- ? ((slotIndex / slotsInEpoch) * 100).toFixed(1) + '%'
243
- : '?';
244
-
245
- console.log(` ${C.dim}Epoch:${C.reset} ${C.bright}${ep}${C.reset} ${C.dim}Slot in epoch:${C.reset} ${slotIndex}/${slotsInEpoch} ${C.dim}(${progress})${C.reset}`);
246
- if (epochData.absolute_slot !== undefined) {
247
- console.log(` ${C.dim}Absolute slot:${C.reset} ${formatNumber(epochData.absolute_slot)}`);
248
- }
249
- if (epochData.block_height !== undefined) {
250
- console.log(` ${C.dim}Block height:${C.reset} ${formatNumber(epochData.block_height)}`);
251
- }
252
- console.log();
253
- }
254
-
255
- // Supply if available
256
- if (supply && !supply.error) {
257
- console.log(` ${C.bright}── Token Supply ───────────────────────────────────────────────${C.reset}`);
258
- if (supply.total !== undefined) {
259
- const totalAETH = (supply.total / 1e9).toFixed(2);
260
- console.log(` ${C.dim}Total supply:${C.reset} ${C.green}${formatNumber(supply.total)} lamports${C.reset} ${C.dim}(${totalAETH} AETH)${C.reset}`);
261
- }
262
- if (supply.circulating !== undefined) {
263
- const circAETH = (supply.circulating / 1e9).toFixed(2);
264
- console.log(` ${C.dim}Circulating:${C.reset} ${formatNumber(supply.circulating)} lamports ${C.dim}(${circAETH} AETH)${C.reset}`);
265
- }
266
- console.log();
267
- }
268
-
269
- console.log(` ${C.dim}Tip: --peers for peer list | --epoch for consensus | --json for raw data${C.reset}`);
270
- console.log();
271
- }
272
-
273
- function renderPeers(peers, rpc) {
274
- console.log();
275
- console.log(`${C.bright}${C.cyan}── Peer List ─────────────────────────────────────────────────${C.reset}`);
276
- console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
277
- console.log();
278
-
279
- if (!peers || peers.length === 0) {
280
- console.log(` ${C.yellow}⚠ No peer information available from this RPC.${C.reset}`);
281
- console.log(` ${C.dim} Peers may not be exposed by your validator's RPC configuration.${C.reset}`);
282
- console.log();
283
- return;
284
- }
285
-
286
- console.log(` ${C.bright}┌────────────────────────────────────────────────────────────────────────┐${C.reset}`);
287
- console.log(` ${C.bright}│${C.reset} ${C.cyan}#${C.reset} ${C.cyan}Validator Address${C.reset} ${C.cyan}Tier${C.reset} ${C.cyan}Score${C.reset} ${C.cyan}Uptime${C.reset} ${C.bright}│${C.reset}`);
288
- console.log(` ${C.bright}├${C.reset}${'-'.repeat(78)}${C.bright}│${C.reset}`);
289
-
290
- peers.slice(0, 50).forEach((peer, i) => {
291
- const num = (i + 1).toString().padStart(2);
292
- const addr = (peer.address || peer.pubkey || peer.id || 'unknown').slice(0, 32).padEnd(34);
293
- const tier = (peer.tier || peer.node_type || '?').toUpperCase().padEnd(6).slice(0, 6);
294
- const score = peer.score !== undefined ? peer.score : (peer.uptime !== undefined ? Math.round(peer.uptime * 100) : null);
295
- const scoreStr = score !== null ? `${score}%` : '?';
296
- const uptime = uptimeString(peer.uptime_seconds || peer.uptime);
297
- const scoreColor = score === null ? C.dim : score >= 80 ? C.green : score >= 50 ? C.yellow : C.red;
298
-
299
- console.log(
300
- ` ${C.bright}│${C.reset} ${C.dim}${num}${C.reset} ${addr} ${tier.padEnd(6)} ${scoreColor}${(scoreStr + '%').padEnd(7)}${C.reset} ${C.dim}${uptime}${C.reset} ${C.bright}│${C.reset}`
301
- );
302
- });
303
-
304
- if (peers.length > 50) {
305
- console.log(` ${C.bright}│${C.reset} ${C.dim}... and ${peers.length - 50} more peers (use --json for full list)${C.reset}`.padEnd(80) + `${C.bright}│${C.reset}`);
306
- }
307
-
308
- console.log(` ${C.bright}└────────────────────────────────────────────────────────────────────────┘${C.reset}`);
309
- console.log();
310
- console.log(` ${C.dim}Total peers: ${peers.length}${C.reset}`);
311
- console.log();
312
- }
313
-
314
- function renderEpoch(epochData, rpc) {
315
- console.log();
316
- console.log(`${C.bright}${C.cyan}── Epoch / Consensus ────────────────────────────────────────${C.reset}`);
317
- console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
318
- console.log();
319
-
320
- if (!epochData) {
321
- console.log(` ${C.yellow}⚠ Epoch information not available.${C.reset}`);
322
- console.log(` ${C.dim} Is your validator fully synced?${C.reset}`);
323
- console.log();
324
- return;
325
- }
326
-
327
- const t = (label, val) => console.log(` ${C.dim}${label}:${C.reset} ${val !== undefined && val !== null ? C.bright + val : C.dim + 'N/A'}${C.reset}`);
328
-
329
- console.log(` ${C.bright}┌─────────────────────────────────────────────────────┐${C.reset}`);
330
- console.log(` ${C.bright}│${C.reset} ${C.cyan}Epoch${C.reset}${' '.repeat(45)}${C.bright}│${C.reset}`);
331
- const ep = epochData.epoch !== undefined ? epochData.epoch : '?';
332
- console.log(` ${C.bright}│${C.reset} ${C.green}${(ep + '').padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
333
- console.log(` ${C.bright}├${C.reset}${'─'.repeat(58)}${C.bright}│${C.reset}`);
334
-
335
- if (epochData.slots_in_epoch !== undefined) {
336
- console.log(` ${C.bright}│${C.reset} ${C.dim}Slots in epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
337
- console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.slots_in_epoch).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
338
- }
339
-
340
- if (epochData.slot_index !== undefined) {
341
- console.log(` ${C.bright}│${C.reset} ${C.dim}Current slot in epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
342
- const progress = epochData.slots_in_epoch > 0
343
- ? `${epochData.slot_index} / ${epochData.slots_in_epoch} (${((epochData.slot_index / epochData.slots_in_epoch) * 100).toFixed(1)}%)`
344
- : `${epochData.slot_index}`;
345
- console.log(` ${C.bright}│${C.reset} ${C.bright}${progress.padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
346
- }
347
-
348
- if (epochData.absolute_slot !== undefined) {
349
- console.log(` ${C.bright}│${C.reset} ${C.dim}Absolute slot${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
350
- console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.absolute_slot).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
351
- }
352
-
353
- if (epochData.block_height !== undefined) {
354
- console.log(` ${C.bright}│${C.reset} ${C.dim}Block height${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
355
- console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.block_height).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
356
- }
357
-
358
- if (epochData.epoch_schedule) {
359
- const es = epochData.epoch_schedule;
360
- if (es.first_normal_epoch !== undefined) {
361
- console.log(` ${C.bright}│${C.reset} ${C.dim}First normal epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
362
- console.log(` ${C.bright}│${C.reset} ${C.bright}${es.first_normal_epoch.padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
363
- }
364
- }
365
-
366
- console.log(` ${C.bright}└─────────────────────────────────────────────────────┘${C.reset}`);
367
- console.log();
368
- }
369
-
370
- // ---------------------------------------------------------------------------
371
- // Main
372
- // ---------------------------------------------------------------------------
373
-
374
- async function main() {
375
- const opts = parseArgs();
376
- const rpc = opts.rpc;
377
-
378
- if (!opts.asJson) {
379
- console.log(`\n${C.cyan}Querying Aether network...${C.reset} ${C.dim}(${rpc})${C.reset}\n`);
380
- }
381
-
382
- // Fetch all data in parallel
383
- const [slot, blockHeight, validators, epochData, tps, supply] = await Promise.all([
384
- getSlot(rpc),
385
- getBlockHeight(rpc),
386
- getValidators(rpc),
387
- getEpoch(rpc),
388
- getTPS(rpc),
389
- getSupply(rpc),
390
- ]);
391
-
392
- const peerCount = Array.isArray(validators) ? validators.length : 0;
393
-
394
- const data = {
395
- slot,
396
- blockHeight,
397
- peerCount,
398
- tps,
399
- supply,
400
- epochData,
401
- validators,
402
- rpc,
403
- fetchedAt: new Date().toISOString(),
404
- };
405
-
406
- if (opts.asJson) {
407
- console.log(JSON.stringify(data, null, 2));
408
- return;
409
- }
410
-
411
- if (opts.showPeers) {
412
- renderPeers(validators, rpc);
413
- } else if (opts.showEpoch) {
414
- renderEpoch(epochData, rpc);
415
- } else {
416
- renderSummary(data, rpc);
417
- }
418
- }
419
-
420
- module.exports = { main, networkCommand: main };
421
-
422
- if (require.main === module) {
423
- main().catch((err) => {
424
- console.error(`\n${C.red}✗ Network command failed:${C.reset} ${err.message}`);
425
- console.error(` ${C.dim}Check that your validator is running and RPC is accessible.${C.reset}`);
426
- console.error(` ${C.dim}Set custom RPC: AETHER_RPC=http://your-rpc-url${C.reset}\n`);
427
- process.exit(1);
428
- });
429
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli network - Aether Network Status
4
+ *
5
+ * Queries the Aether network for broad health metrics:
6
+ * - Network-wide slot, block height, slot production
7
+ * - Connected peers and their info
8
+ * - Consensus status and epoch info
9
+ * - TPS estimates from the network
10
+ *
11
+ * FULLY WIRED TO SDK - Uses @jellylegsai/aether-sdk for all blockchain calls.
12
+ * No manual HTTP - all calls go through AetherClient with real RPC.
13
+ *
14
+ * Usage:
15
+ * aether network # Interactive summary view
16
+ * aether network --json # JSON output for scripting
17
+ * aether network --rpc <url> # Query a specific RPC endpoint
18
+ * aether network --peers # Detailed peer list
19
+ * aether network --epoch # Current epoch and consensus info
20
+ * aether network --wait # Wait for node to sync
21
+ * aether network --ping # Include latency measurements
22
+ *
23
+ * SDK Methods Used:
24
+ * - client.getSlot() → GET /v1/slot
25
+ * - client.getBlockHeight() → GET /v1/blockheight
26
+ * - client.getValidators() → GET /v1/validators
27
+ * - client.getEpochInfo() → GET /v1/epoch
28
+ * - client.getTPS() → GET /v1/tps
29
+ * - client.getSupply() → GET /v1/supply
30
+ * - client.getHealth() → GET /v1/health
31
+ * - client.getVersion() → GET /v1/version
32
+ * - client.getSlotProduction() → POST /v1/slot_production
33
+ * - client.ping() → Health check with latency
34
+ */
35
+
36
+ const path = require('path');
37
+
38
+ // Import SDK for real blockchain RPC calls
39
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
40
+ const aether = require(sdkPath);
41
+
42
+ // Import UI framework
43
+ const { C, indicators, startSpinner, stopSpinner, drawBox, drawTable,
44
+ success, error, warning, info, code, highlight, value,
45
+ formatHealth, formatLatency } = require('../lib/ui');
46
+
47
+ const DEFAULT_RPC = process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
48
+
49
+ // ============================================================================
50
+ // SDK Client Setup
51
+ // ============================================================================
52
+
53
+ function createClient(rpc) {
54
+ return new aether.AetherClient({ rpcUrl: rpc });
55
+ }
56
+
57
+ // ============================================================================
58
+ // Argument Parsing
59
+ // ============================================================================
60
+
61
+ function parseArgs() {
62
+ const args = process.argv.slice(2);
63
+ const options = {
64
+ rpc: DEFAULT_RPC,
65
+ showPeers: false,
66
+ showEpoch: false,
67
+ asJson: false,
68
+ wait: false,
69
+ doPing: false,
70
+ };
71
+
72
+ for (let i = 0; i < args.length; i++) {
73
+ switch (args[i]) {
74
+ case '-r':
75
+ case '--rpc':
76
+ options.rpc = args[++i];
77
+ break;
78
+ case '-p':
79
+ case '--peers':
80
+ options.showPeers = true;
81
+ break;
82
+ case '-e':
83
+ case '--epoch':
84
+ options.showEpoch = true;
85
+ break;
86
+ case '-j':
87
+ case '--json':
88
+ options.asJson = true;
89
+ break;
90
+ case '-w':
91
+ case '--wait':
92
+ options.wait = true;
93
+ break;
94
+ case '--ping':
95
+ options.doPing = true;
96
+ break;
97
+ case '-h':
98
+ case '--help':
99
+ showHelp();
100
+ process.exit(0);
101
+ }
102
+ }
103
+
104
+ return options;
105
+ }
106
+
107
+ function showHelp() {
108
+ console.log(`
109
+ ${C.cyan}${C.bright}network${C.reset} Aether Network Status
110
+
111
+ ${C.bright}USAGE${C.reset}
112
+ aether network [options]
113
+
114
+ ${C.bright}OPTIONS${C.reset}
115
+ -r, --rpc <url> RPC endpoint (default: ${DEFAULT_RPC})
116
+ -p, --peers Show detailed peer list
117
+ -e, --epoch Show epoch and consensus information
118
+ -j, --json Output raw JSON for scripting
119
+ -w, --wait Wait for node to sync
120
+ --ping Include latency measurements
121
+ -h, --help Show this help message
122
+
123
+ ${C.bright}SDK METHODS${C.reset}
124
+ getSlot(), getBlockHeight(), getValidators(), getEpochInfo()
125
+ getTPS(), getSupply(), getHealth(), getVersion(), getSlotProduction()
126
+
127
+ ${C.bright}EXAMPLES${C.reset}
128
+ aether network # Summary view
129
+ aether network --json # JSON output
130
+ aether network --peers # Detailed peer list
131
+ aether network --epoch # Epoch info
132
+ aether network --rpc http://my-rpc:8899
133
+ `);
134
+ }
135
+
136
+ // ============================================================================
137
+ // SDK Data Fetchers - REAL RPC CALLS
138
+ // ============================================================================
139
+
140
+ async function fetchNetworkData(rpc, asJson) {
141
+ const client = createClient(rpc);
142
+
143
+ const startTime = Date.now();
144
+
145
+ if (!asJson) {
146
+ startSpinner('Querying network via SDK');
147
+ }
148
+
149
+ const results = await Promise.allSettled([
150
+ client.getSlot().catch(() => null),
151
+ client.getBlockHeight().catch(() => null),
152
+ client.getValidators().catch(() => []),
153
+ client.getEpochInfo().catch(() => null),
154
+ client.getTPS().catch(() => null),
155
+ client.getSupply().catch(() => null),
156
+ client.getHealth().catch(() => null),
157
+ client.getVersion().catch(() => null),
158
+ client.getSlotProduction().catch(() => null),
159
+ aether.ping(rpc).catch(() => ({ ok: false, latency: null })),
160
+ ]);
161
+
162
+ const latency = Date.now() - startTime;
163
+
164
+ if (!asJson) {
165
+ stopSpinner(true, 'Network data retrieved');
166
+ }
167
+
168
+ const [
169
+ slot,
170
+ blockHeight,
171
+ validators,
172
+ epochInfo,
173
+ tps,
174
+ supply,
175
+ health,
176
+ version,
177
+ slotProduction,
178
+ pingResult,
179
+ ] = results.map(r => r.status === 'fulfilled' ? r.value : null);
180
+
181
+ return {
182
+ slot,
183
+ blockHeight,
184
+ validators: Array.isArray(validators) ? validators : [],
185
+ epochInfo,
186
+ tps,
187
+ supply,
188
+ health,
189
+ version,
190
+ slotProduction,
191
+ pingResult,
192
+ latency,
193
+ rpc,
194
+ fetchedAt: new Date().toISOString(),
195
+ };
196
+ }
197
+
198
+ // ============================================================================
199
+ // Format Helpers
200
+ // ============================================================================
201
+
202
+ function formatNumber(n) {
203
+ if (n === null || n === undefined) return `${C.dim}N/A${C.reset}`;
204
+ return n.toLocaleString();
205
+ }
206
+
207
+ function formatAether(lamports) {
208
+ if (!lamports && lamports !== 0) return `${C.dim}N/A${C.reset}`;
209
+ const aeth = Number(lamports) / 1e9;
210
+ if (aeth === 0) return '0 AETH';
211
+ return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
212
+ }
213
+
214
+ function uptimeString(seconds) {
215
+ if (!seconds) return `${C.dim}unknown${C.reset}`;
216
+ const d = Math.floor(seconds / 86400);
217
+ const h = Math.floor((seconds % 86400) / 3600);
218
+ const m = Math.floor((seconds % 3600) / 60);
219
+ const parts = [];
220
+ if (d > 0) parts.push(`${d}d`);
221
+ if (h > 0) parts.push(`${h}h`);
222
+ if (m > 0) parts.push(`${m}m`);
223
+ return parts.length > 0 ? parts.join(' ') : `${seconds}s`;
224
+ }
225
+
226
+ function statusColor(status) {
227
+ const s = (status || '').toLowerCase();
228
+ if (s === 'active' || s === 'ok' || s === 'healthy') return C.green;
229
+ if (s === 'delinquent' || s === 'error') return C.red;
230
+ if (s === 'inactive' || s === 'syncing') return C.yellow;
231
+ return C.dim;
232
+ }
233
+
234
+ // ============================================================================
235
+ // Output Renderers
236
+ // ============================================================================
237
+
238
+ function renderSummary(data) {
239
+ const { slot, blockHeight, validators, epochInfo, tps, supply, health, version, pingResult, latency } = data;
240
+ const peerCount = validators.length;
241
+
242
+ // Health status
243
+ const isHealthy = health === 'ok' || health === 'healthy';
244
+ const healthStatus = isHealthy ?
245
+ `${C.green}${indicators.success} Healthy${C.reset}` :
246
+ health ? `${C.yellow}${indicators.warning} ${health}${C.reset}` :
247
+ `${C.red}${indicators.error} Unknown${C.reset}`;
248
+
249
+ // Version string
250
+ const versionStr = version ?
251
+ (version.aetherCore || version.featureSet || JSON.stringify(version)) :
252
+ `${C.dim}unknown${C.reset}`;
253
+
254
+ console.log();
255
+ console.log(drawBox(
256
+ `
257
+ ${C.bright}AETHER NETWORK STATUS${C.reset} ${C.dim}${data.fetchedAt}${C.reset}
258
+
259
+ ${C.cyan}Health:${C.reset} ${healthStatus}
260
+ ${C.cyan}RPC:${C.reset} ${data.rpc}
261
+ ${C.cyan}Version:${C.reset} ${versionStr}
262
+ ${C.cyan}Latency:${C.reset} ${formatLatency(pingResult?.latency || latency)}
263
+
264
+ ${C.cyan}Current Slot:${C.reset} ${highlight(formatNumber(slot))}
265
+ ${C.cyan}Block Height:${C.reset} ${C.green}${formatNumber(blockHeight)}${C.reset}
266
+ ${C.cyan}Active Peers:${C.reset} ${C.magenta}${formatNumber(peerCount)}${C.reset}
267
+ ${C.cyan}TPS:${C.reset} ${tps !== null ? `${C.cyan}${tps.toFixed(2)}${C.reset}` : `${C.dim}N/A${C.reset}`}
268
+
269
+ ${epochInfo ? `${C.cyan}Epoch:${C.reset} ${C.bright}${epochInfo.epoch}${C.reset} (${formatNumber(epochInfo.slotIndex)}/${formatNumber(epochInfo.slotsInEpoch)} slots)` : ''}
270
+ ${supply ? `${C.cyan}Total Supply:${C.reset} ${C.green}${formatAether(supply.total)}${C.reset}` : ''}
271
+
272
+ ${C.dim}SDK: @jellylegsai/aether-sdk${C.reset}
273
+ `.trim(),
274
+ { style: 'double', title: 'AETHER NETWORK', titleColor: C.cyan + C.bright }
275
+ ));
276
+
277
+ console.log();
278
+ }
279
+
280
+ function renderPeers(validators, rpc) {
281
+ if (!validators || validators.length === 0) {
282
+ console.log(`\n ${warning('No peer information available')}`);
283
+ console.log(` ${C.dim}Peers may not be exposed by your validator's RPC configuration.${C.reset}\n`);
284
+ return;
285
+ }
286
+
287
+ const rows = validators.slice(0, 50).map((v, i) => {
288
+ const addr = (v.address || v.pubkey || v.id || v.vote_account || 'unknown').slice(0, 32);
289
+ const tier = (v.tier || v.node_type || 'unknown').toUpperCase();
290
+ const stake = formatAether(v.stake_lamports || v.stake || v.activated_stake || 0);
291
+ const status = v.status || 'active';
292
+ const statusCol = statusColor(status);
293
+
294
+ return [
295
+ `${statusCol}●${C.reset}`,
296
+ `${i + 1}`,
297
+ addr,
298
+ tier,
299
+ stake,
300
+ ];
301
+ });
302
+
303
+ console.log();
304
+ console.log(drawTable(
305
+ ['', '#', 'Validator', 'Tier', 'Stake'],
306
+ rows,
307
+ { borderStyle: 'single', headerColor: C.cyan + C.bright }
308
+ ));
309
+
310
+ if (validators.length > 50) {
311
+ console.log(`\n ${C.dim}... and ${validators.length - 50} more validators (use --json for full list)${C.reset}`);
312
+ }
313
+
314
+ console.log();
315
+ console.log(` ${C.bright}Total validators:${C.reset} ${C.magenta}${validators.length}${C.reset}`);
316
+ console.log();
317
+ }
318
+
319
+ function renderEpoch(epochInfo) {
320
+ console.log();
321
+
322
+ if (!epochInfo) {
323
+ console.log(`\n ${warning('Epoch information not available')}`);
324
+ console.log(` ${C.dim}Is your validator fully synced?${C.reset}\n`);
325
+ return;
326
+ }
327
+
328
+ const progress = epochInfo.slotsInEpoch > 0 ?
329
+ ((epochInfo.slotIndex / epochInfo.slotsInEpoch) * 100).toFixed(2) : '0.00';
330
+
331
+ console.log(drawBox(
332
+ `
333
+ ${C.bright}Epoch ${epochInfo.epoch}${C.reset}
334
+
335
+ ${C.cyan}Slots in Epoch:${C.reset} ${formatNumber(epochInfo.slotsInEpoch)}
336
+ ${C.cyan}Current Slot:${C.reset} ${formatNumber(epochInfo.slotIndex)}
337
+ ${C.cyan}Progress:${C.reset} ${highlight(progress + '%')}
338
+ ${C.cyan}Blocks Remaining:${C.reset} ${formatNumber(epochInfo.slotsInEpoch - epochInfo.slotIndex)}
339
+
340
+ ${epochInfo.absoluteSlot ? `${C.cyan}Absolute Slot:${C.reset} ${formatNumber(epochInfo.absoluteSlot)}` : ''}
341
+ ${epochInfo.blockHeight ? `${C.cyan}Block Height:${C.reset} ${formatNumber(epochInfo.blockHeight)}` : ''}
342
+ `.trim(),
343
+ { style: 'single', title: 'EPOCH INFO', titleColor: C.cyan }
344
+ ));
345
+
346
+ // Progress bar
347
+ const barWidth = 40;
348
+ const filled = Math.floor((progress / 100) * barWidth);
349
+ const empty = barWidth - filled;
350
+ const progressBar = C.green + '█'.repeat(filled) + C.dim + '░'.repeat(empty) + C.reset;
351
+
352
+ console.log(`\n ${progressBar} ${C.bright}${progress}%${C.reset}\n`);
353
+ }
354
+
355
+ function renderJson(data) {
356
+ console.log(JSON.stringify(data, (key, value) => {
357
+ if (typeof value === 'bigint') return value.toString();
358
+ return value;
359
+ }, 2));
360
+ }
361
+
362
+ // ============================================================================
363
+ // Main Command
364
+ // ============================================================================
365
+
366
+ async function networkCommand() {
367
+ const opts = parseArgs();
368
+
369
+ try {
370
+ const data = await fetchNetworkData(opts.rpc, opts.asJson);
371
+
372
+ if (opts.asJson) {
373
+ renderJson(data);
374
+ return;
375
+ }
376
+
377
+ if (opts.showPeers) {
378
+ renderPeers(data.validators, opts.rpc);
379
+ } else if (opts.showEpoch) {
380
+ renderEpoch(data.epochInfo);
381
+ } else {
382
+ renderSummary(data);
383
+ }
384
+
385
+ } catch (err) {
386
+ if (opts.asJson) {
387
+ console.log(JSON.stringify({
388
+ error: err.message,
389
+ rpc: opts.rpc,
390
+ timestamp: new Date().toISOString(),
391
+ }, null, 2));
392
+ } else {
393
+ console.log(`\n ${error('Network query failed')}`);
394
+ console.log(` ${C.dim}${err.message}${C.reset}\n`);
395
+ console.log(` ${C.bright}Troubleshooting:${C.reset}`);
396
+ console.log(` • Is your validator running? ${code('aether ping')}`);
397
+ console.log(` • Check RPC endpoint: ${C.dim}${opts.rpc}${C.reset}`);
398
+ console.log(` • Set custom RPC: ${C.dim}AETHER_RPC=https://your-rpc-url${C.reset}`);
399
+ console.log();
400
+ }
401
+ process.exit(1);
402
+ }
403
+ }
404
+
405
+ module.exports = { networkCommand, main: networkCommand };
406
+
407
+ if (require.main === module) {
408
+ networkCommand().catch(err => {
409
+ console.error(`\n ${error('Network command failed')}: ${err.message}\n`);
410
+ process.exit(1);
411
+ });
412
+ }