aether-hub 1.2.6 → 1.2.8

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/commands/ping.js CHANGED
@@ -1,320 +1,266 @@
1
- #!/usr/bin/env node
2
- /**
3
- * aether-cli ping
4
- *
5
- * Quick RPC health check — measures latency, verifies connectivity,
6
- * and reports node version and slot info.
7
- *
8
- * Usage:
9
- * aether ping Ping default RPC (AETHER_RPC or localhost:8899)
10
- * aether ping --rpc <url> Ping a specific RPC endpoint
11
- * aether ping --count <n> Run <n> pings and show avg/min/max (default: 1, max 20)
12
- * aether ping --json JSON output for scripting/monitoring
13
- *
14
- * Examples:
15
- * aether ping # Single ping, default RPC
16
- * aether ping --rpc https://rpc.example.com # Ping specific endpoint
17
- * aether ping --count 5 --json # 5 pings, JSON output for alerting
18
- */
19
-
20
- const http = require('http');
21
- const https = require('https');
22
-
23
- // ANSI colours
24
- const C = {
25
- reset: '\x1b[0m',
26
- bright: '\x1b[1m',
27
- dim: '\x1b[2m',
28
- red: '\x1b[31m',
29
- green: '\x1b[32m',
30
- yellow: '\x1b[33m',
31
- cyan: '\x1b[36m',
32
- };
33
-
34
- const CLI_VERSION = '1.0.6';
35
-
36
- // ---------------------------------------------------------------------------
37
- // Helpers
38
- // ---------------------------------------------------------------------------
39
-
40
- function getDefaultRpc() {
41
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
42
- }
43
-
44
- function httpRequest(rpcUrl, pathStr, timeoutMs = 8000) {
45
- return new Promise((resolve, reject) => {
46
- const url = new URL(pathStr, rpcUrl);
47
- const lib = url.protocol === 'https:' ? https : http;
48
- const req = lib.request({
49
- hostname: url.hostname,
50
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
51
- path: url.pathname + url.search,
52
- method: 'GET',
53
- timeout: timeoutMs,
54
- headers: { 'Content-Type': 'application/json' },
55
- }, (res) => {
56
- let data = '';
57
- res.on('data', (chunk) => data += chunk);
58
- res.on('end', () => {
59
- try { resolve(JSON.parse(data)); }
60
- catch { resolve({ raw: data }); }
61
- });
62
- });
63
- req.on('error', reject);
64
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
65
- req.end();
66
- });
67
- }
68
-
69
- function httpPost(rpcUrl, pathStr, body, timeoutMs = 8000) {
70
- return new Promise((resolve, reject) => {
71
- const url = new URL(pathStr, rpcUrl);
72
- const lib = url.protocol === 'https:' ? https : http;
73
- const bodyStr = JSON.stringify(body);
74
- const req = lib.request({
75
- hostname: url.hostname,
76
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
77
- path: url.pathname + url.search,
78
- method: 'POST',
79
- timeout: timeoutMs,
80
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) },
81
- }, (res) => {
82
- let data = '';
83
- res.on('data', (chunk) => data += chunk);
84
- res.on('end', () => {
85
- try { resolve(JSON.parse(data)); }
86
- catch { resolve({ raw: data }); }
87
- });
88
- });
89
- req.on('error', reject);
90
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
91
- req.write(bodyStr);
92
- req.end();
93
- });
94
- }
95
-
96
- // ---------------------------------------------------------------------------
97
- // Single ping: measure latency to /v1/slot
98
- // ---------------------------------------------------------------------------
99
-
100
- async function pingOnce(rpcUrl) {
101
- const start = Date.now();
102
- let slot = null;
103
- let error = null;
104
- let latencyMs = null;
105
-
106
- try {
107
- // Use POST to /v1/slot (some nodes only support POST)
108
- const result = await httpPost(rpcUrl, '/v1/slot', {}, 8000);
109
- latencyMs = Date.now() - start;
110
-
111
- if (result && result.error) {
112
- error = result.error;
113
- } else {
114
- slot = result.slot ?? result;
115
- if (typeof slot === 'object') slot = slot.slot;
116
- }
117
- } catch (err) {
118
- latencyMs = Date.now() - start;
119
- error = err.message;
120
- }
121
-
122
- return { latencyMs, slot, error, rpcUrl };
123
- }
124
-
125
- // ---------------------------------------------------------------------------
126
- // Multi-ping: run N pings and aggregate
127
- // ---------------------------------------------------------------------------
128
-
129
- async function pingMulti(rpcUrl, count) {
130
- const results = [];
131
- for (let i = 0; i < count; i++) {
132
- results.push(await pingOnce(rpcUrl));
133
- if (i < count - 1) await new Promise(r => setTimeout(r, 100));
134
- }
135
- return results;
136
- }
137
-
138
- // ---------------------------------------------------------------------------
139
- // Colour helpers
140
- // ---------------------------------------------------------------------------
141
-
142
- function latencyColor(ms) {
143
- if (ms === null) return C.red;
144
- if (ms < 50) return C.green;
145
- if (ms < 200) return C.cyan;
146
- if (ms < 500) return C.yellow;
147
- return C.red;
148
- }
149
-
150
- function latencyLabel(ms) {
151
- if (ms === null) return '✗ unreachable';
152
- if (ms < 50) return `● ${ms}ms (excellent)`;
153
- if (ms < 200) return `● ${ms}ms (good)`;
154
- if (ms < 500) return `○ ${ms}ms (fair)`;
155
- return `○ ${ms}ms (slow)`;
156
- }
157
-
158
- // ---------------------------------------------------------------------------
159
- // Output formatters
160
- // ---------------------------------------------------------------------------
161
-
162
- function printResult(ping, asJson) {
163
- const { latencyMs, slot, error, rpcUrl } = ping;
164
-
165
- if (asJson) {
166
- console.log(JSON.stringify({
167
- rpc: rpcUrl,
168
- online: error === null,
169
- latency_ms: latencyMs,
170
- slot,
171
- error: error || null,
172
- cli_version: CLI_VERSION,
173
- timestamp: new Date().toISOString(),
174
- }));
175
- return;
176
- }
177
-
178
- const lc = latencyColor(latencyMs);
179
- const bar = latencyMs !== null ? '█'.repeat(Math.min(10, Math.floor(latencyMs / 50))) : '▒';
180
-
181
- console.log(` ${lc}${bar}${C.reset} ${C.bright}${latencyLabel(latencyMs)}${C.reset}`);
182
- if (slot !== null) {
183
- console.log(` ${C.dim} slot: ${C.reset}${C.cyan}${slot.toLocaleString()}${C.reset}`);
184
- }
185
- if (error) {
186
- console.log(` ${C.red} ✗ ${error}${C.reset}`);
187
- }
188
- console.log(` ${C.dim} rpc: ${rpcUrl}${C.reset}`);
189
- }
190
-
191
- function printAggregated(results, rpcUrl, asJson) {
192
- const online = results.filter(r => r.error === null);
193
- const failed = results.filter(r => r.error !== null);
194
-
195
- if (asJson) {
196
- const latencies = online.map(r => r.latencyMs).filter(Boolean);
197
- const avg = latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
198
- const min = latencies.length > 0 ? Math.min(...latencies) : null;
199
- const max = latencies.length > 0 ? Math.max(...latencies) : null;
200
-
201
- console.log(JSON.stringify({
202
- rpc: rpcUrl,
203
- count: results.length,
204
- online: online.length,
205
- failed: failed.length,
206
- latency_ms: { avg, min, max },
207
- slots: online.map(r => r.slot).filter(Boolean),
208
- errors: failed.map(r => r.error),
209
- cli_version: CLI_VERSION,
210
- timestamp: new Date().toISOString(),
211
- }));
212
- return;
213
- }
214
-
215
- console.log(`\n${C.bright}${C.cyan}── Ping Results: ${rpcUrl} ──${C.reset}\n`);
216
-
217
- const latencies = online.map(r => r.latencyMs).filter(Boolean);
218
-
219
- if (latencies.length > 0) {
220
- const avg = Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length);
221
- const min = Math.min(...latencies);
222
- const max = Math.max(...latencies);
223
-
224
- console.log(` ${C.green}✓${C.reset} ${online.length}/${results.length} successful\n`);
225
-
226
- // Per-ping bars
227
- for (let i = 0; i < online.length; i++) {
228
- const r = online[i];
229
- const lc = latencyColor(r.latencyMs);
230
- const bar = '█'.repeat(Math.min(10, Math.floor(r.latencyMs / 50)));
231
- const slotStr = r.slot !== null ? ` slot=${C.cyan}${r.slot.toLocaleString()}${C.reset}` : '';
232
- console.log(` ${lc}${bar}${C.reset} ${r.latencyMs}ms${slotStr}`);
233
- }
234
-
235
- console.log();
236
- console.log(` ${C.bright}Latency:${C.reset} avg=${latencyColor(avg)}${avg}ms${C.reset} min=${latencyColor(min)}${min}ms${C.reset} max=${latencyColor(max)}${max}ms${C.reset}`);
237
- console.log(` ${C.dim} Packets: ${results.length} Lost: ${failed.length}${C.reset}`);
238
-
239
- // Health assessment
240
- const healthPct = (online.length / results.length) * 100;
241
- if (healthPct === 100 && avg < 50) {
242
- console.log(` ${C.green} Health: excellent${C.reset}`);
243
- } else if (healthPct >= 80 && avg < 200) {
244
- console.log(` ${C.cyan} Health: good${C.reset}`);
245
- } else if (healthPct >= 60) {
246
- console.log(` ${C.yellow} Health: degraded${C.reset}`);
247
- } else {
248
- console.log(` ${C.red} Health: poor${C.reset}`);
249
- }
250
- } else {
251
- console.log(` ${C.red}✗ All pings failed${C.reset}`);
252
- for (const r of failed) {
253
- console.log(` ${C.red}✗ ${r.error}${C.reset}`);
254
- }
255
- }
256
-
257
- if (failed.length > 0 && online.length > 0) {
258
- console.log();
259
- console.log(` ${C.yellow}⚠ ${failed.length} pings failed:${C.reset}`);
260
- for (const r of failed) {
261
- console.log(` ${C.red}✗ ${r.error}${C.reset}`);
262
- }
263
- }
264
- console.log();
265
- }
266
-
267
- // ---------------------------------------------------------------------------
268
- // Main
269
- // ---------------------------------------------------------------------------
270
-
271
- async function main() {
272
- const args = process.argv.slice(3); // [node, index.js, ping, ...]
273
-
274
- const rpcIndex = args.findIndex(a => a === '--rpc' || a === '-r');
275
- const rpcUrl = rpcIndex !== -1 && args[rpcIndex + 1] && !args[rpcIndex + 1].startsWith('-')
276
- ? args[rpcIndex + 1]
277
- : getDefaultRpc();
278
-
279
- const countIndex = args.findIndex(a => a === '--count' || a === '-c');
280
- const countRaw = countIndex !== -1 && args[countIndex + 1] && !args[countIndex + 1].startsWith('-')
281
- ? parseInt(args[countIndex + 1], 10)
282
- : 1;
283
- const count = Math.min(Math.max(1, countRaw || 1), 20);
284
-
285
- const asJson = args.includes('--json') || args.includes('-j');
286
-
287
- if (!asJson) {
288
- console.log(`\n${C.bright}${C.cyan}── Aether RPC Ping ──────────────────────────────────────${C.reset}`);
289
- if (count > 1) {
290
- console.log(` ${C.dim}Running ${count} pings against ${rpcUrl}…${C.reset}`);
291
- } else {
292
- console.log(` ${C.dim}RPC: ${rpcUrl}${C.reset}`);
293
- }
294
- console.log();
295
- }
296
-
297
- if (count === 1) {
298
- const result = await pingOnce(rpcUrl);
299
- printResult(result, asJson);
300
- if (!asJson) console.log();
301
- // Exit 1 if unreachable
302
- if (result.error) process.exit(1);
303
- } else {
304
- const results = await pingMulti(rpcUrl, count);
305
- printAggregated(results, rpcUrl, asJson);
306
- // Exit 1 if all failed
307
- if (results.every(r => r.error)) process.exit(1);
308
- }
309
- }
310
-
311
- main().catch(err => {
312
- console.error(`\n${C.red}✗ Ping failed:${C.reset} ${err.message}\n`);
313
- process.exit(1);
314
- });
315
-
316
- module.exports = { pingCommand: main };
317
-
318
- if (require.main === module) {
319
- main();
320
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli ping
4
+ *
5
+ * Quick RPC health check — measures latency, verifies connectivity,
6
+ * and reports node version and slot info.
7
+ *
8
+ * Uses @jellylegsai/aether-sdk for REAL HTTP RPC calls.
9
+ *
10
+ * Usage:
11
+ * aether ping Ping default RPC (AETHER_RPC or localhost:8899)
12
+ * aether ping --rpc <url> Ping a specific RPC endpoint
13
+ * aether ping --count <n> Run <n> pings and show avg/min/max (default: 1, max 20)
14
+ * aether ping --json JSON output for scripting/monitoring
15
+ *
16
+ * Examples:
17
+ * aether ping # Single ping, default RPC
18
+ * aether ping --rpc https://rpc.example.com # Ping specific endpoint
19
+ * aether ping --count 5 --json # 5 pings, JSON output for alerting
20
+ */
21
+
22
+ const path = require('path');
23
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
24
+ const aether = require(sdkPath);
25
+
26
+ // ANSI colours
27
+ const C = {
28
+ reset: '\x1b[0m',
29
+ bright: '\x1b[1m',
30
+ dim: '\x1b[2m',
31
+ red: '\x1b[31m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ cyan: '\x1b[36m',
35
+ };
36
+
37
+ const CLI_VERSION = '1.0.6';
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Helpers
41
+ // ---------------------------------------------------------------------------
42
+
43
+ function getDefaultRpc() {
44
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Single ping: measure latency to /v1/slot using SDK
49
+ // ---------------------------------------------------------------------------
50
+
51
+ async function pingOnce(rpcUrl) {
52
+ const client = new aether.AetherClient({ rpcUrl });
53
+ const start = Date.now();
54
+ let slot = null;
55
+ let error = null;
56
+ let latencyMs = null;
57
+
58
+ try {
59
+ // Real RPC call via SDK: POST /v1/slot
60
+ const result = await client.getSlot();
61
+ latencyMs = Date.now() - start;
62
+ slot = result;
63
+ } catch (err) {
64
+ latencyMs = Date.now() - start;
65
+ error = err.message;
66
+ }
67
+
68
+ return { latencyMs, slot, error, rpcUrl };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Multi-ping: run N pings and aggregate
73
+ // ---------------------------------------------------------------------------
74
+
75
+ async function pingMulti(rpcUrl, count) {
76
+ const results = [];
77
+ for (let i = 0; i < count; i++) {
78
+ results.push(await pingOnce(rpcUrl));
79
+ if (i < count - 1) await new Promise(r => setTimeout(r, 100));
80
+ }
81
+ return results;
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Colour helpers
86
+ // ---------------------------------------------------------------------------
87
+
88
+ function latencyColor(ms) {
89
+ if (ms === null) return C.red;
90
+ if (ms < 50) return C.green;
91
+ if (ms < 200) return C.cyan;
92
+ if (ms < 500) return C.yellow;
93
+ return C.red;
94
+ }
95
+
96
+ function latencyLabel(ms) {
97
+ if (ms === null) return '✗ unreachable';
98
+ if (ms < 50) return `● ${ms}ms (excellent)`;
99
+ if (ms < 200) return `● ${ms}ms (good)`;
100
+ if (ms < 500) return `○ ${ms}ms (fair)`;
101
+ return `○ ${ms}ms (slow)`;
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Output formatters
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function printResult(ping, asJson) {
109
+ const { latencyMs, slot, error, rpcUrl } = ping;
110
+
111
+ if (asJson) {
112
+ console.log(JSON.stringify({
113
+ rpc: rpcUrl,
114
+ online: error === null,
115
+ latency_ms: latencyMs,
116
+ slot,
117
+ error: error || null,
118
+ cli_version: CLI_VERSION,
119
+ timestamp: new Date().toISOString(),
120
+ }));
121
+ return;
122
+ }
123
+
124
+ const lc = latencyColor(latencyMs);
125
+ const bar = latencyMs !== null ? '█'.repeat(Math.min(10, Math.floor(latencyMs / 50))) : '▒';
126
+
127
+ console.log(` ${lc}${bar}${C.reset} ${C.bright}${latencyLabel(latencyMs)}${C.reset}`);
128
+ if (slot !== null) {
129
+ console.log(` ${C.dim} slot: ${C.reset}${C.cyan}${slot.toLocaleString()}${C.reset}`);
130
+ }
131
+ if (error) {
132
+ console.log(` ${C.red} ✗ ${error}${C.reset}`);
133
+ }
134
+ console.log(` ${C.dim} rpc: ${rpcUrl}${C.reset}`);
135
+ }
136
+
137
+ function printAggregated(results, rpcUrl, asJson) {
138
+ const online = results.filter(r => r.error === null);
139
+ const failed = results.filter(r => r.error !== null);
140
+
141
+ if (asJson) {
142
+ const latencies = online.map(r => r.latencyMs).filter(Boolean);
143
+ const avg = latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
144
+ const min = latencies.length > 0 ? Math.min(...latencies) : null;
145
+ const max = latencies.length > 0 ? Math.max(...latencies) : null;
146
+
147
+ console.log(JSON.stringify({
148
+ rpc: rpcUrl,
149
+ count: results.length,
150
+ online: online.length,
151
+ failed: failed.length,
152
+ latency_ms: { avg, min, max },
153
+ slots: online.map(r => r.slot).filter(Boolean),
154
+ errors: failed.map(r => r.error),
155
+ cli_version: CLI_VERSION,
156
+ timestamp: new Date().toISOString(),
157
+ }));
158
+ return;
159
+ }
160
+
161
+ console.log(`\n${C.bright}${C.cyan}── Ping Results: ${rpcUrl} ──${C.reset}\n`);
162
+
163
+ const latencies = online.map(r => r.latencyMs).filter(Boolean);
164
+
165
+ if (latencies.length > 0) {
166
+ const avg = Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length);
167
+ const min = Math.min(...latencies);
168
+ const max = Math.max(...latencies);
169
+
170
+ console.log(` ${C.green}✓${C.reset} ${online.length}/${results.length} successful\n`);
171
+
172
+ // Per-ping bars
173
+ for (let i = 0; i < online.length; i++) {
174
+ const r = online[i];
175
+ const lc = latencyColor(r.latencyMs);
176
+ const bar = '█'.repeat(Math.min(10, Math.floor(r.latencyMs / 50)));
177
+ const slotStr = r.slot !== null ? ` slot=${C.cyan}${r.slot.toLocaleString()}${C.reset}` : '';
178
+ console.log(` ${lc}${bar}${C.reset} ${r.latencyMs}ms${slotStr}`);
179
+ }
180
+
181
+ console.log();
182
+ console.log(` ${C.bright}Latency:${C.reset} avg=${latencyColor(avg)}${avg}ms${C.reset} min=${latencyColor(min)}${min}ms${C.reset} max=${latencyColor(max)}${max}ms${C.reset}`);
183
+ console.log(` ${C.dim} Packets: ${results.length} Lost: ${failed.length}${C.reset}`);
184
+
185
+ // Health assessment
186
+ const healthPct = (online.length / results.length) * 100;
187
+ if (healthPct === 100 && avg < 50) {
188
+ console.log(` ${C.green} Health: excellent${C.reset}`);
189
+ } else if (healthPct >= 80 && avg < 200) {
190
+ console.log(` ${C.cyan} Health: good${C.reset}`);
191
+ } else if (healthPct >= 60) {
192
+ console.log(` ${C.yellow} Health: degraded${C.reset}`);
193
+ } else {
194
+ console.log(` ${C.red} Health: poor${C.reset}`);
195
+ }
196
+ } else {
197
+ console.log(` ${C.red}✗ All pings failed${C.reset}`);
198
+ for (const r of failed) {
199
+ console.log(` ${C.red}✗ ${r.error}${C.reset}`);
200
+ }
201
+ }
202
+
203
+ if (failed.length > 0 && online.length > 0) {
204
+ console.log();
205
+ console.log(` ${C.yellow}⚠ ${failed.length} pings failed:${C.reset}`);
206
+ for (const r of failed) {
207
+ console.log(` ${C.red}✗ ${r.error}${C.reset}`);
208
+ }
209
+ }
210
+ console.log();
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Main
215
+ // ---------------------------------------------------------------------------
216
+
217
+ async function main() {
218
+ const args = process.argv.slice(3); // [node, index.js, ping, ...]
219
+
220
+ const rpcIndex = args.findIndex(a => a === '--rpc' || a === '-r');
221
+ const rpcUrl = rpcIndex !== -1 && args[rpcIndex + 1] && !args[rpcIndex + 1].startsWith('-')
222
+ ? args[rpcIndex + 1]
223
+ : getDefaultRpc();
224
+
225
+ const countIndex = args.findIndex(a => a === '--count' || a === '-c');
226
+ const countRaw = countIndex !== -1 && args[countIndex + 1] && !args[countIndex + 1].startsWith('-')
227
+ ? parseInt(args[countIndex + 1], 10)
228
+ : 1;
229
+ const count = Math.min(Math.max(1, countRaw || 1), 20);
230
+
231
+ const asJson = args.includes('--json') || args.includes('-j');
232
+
233
+ if (!asJson) {
234
+ console.log(`\n${C.bright}${C.cyan}── Aether RPC Ping ──────────────────────────────────────${C.reset}`);
235
+ if (count > 1) {
236
+ console.log(` ${C.dim}Running ${count} pings against ${rpcUrl}…${C.reset}`);
237
+ } else {
238
+ console.log(` ${C.dim}RPC: ${rpcUrl}${C.reset}`);
239
+ }
240
+ console.log();
241
+ }
242
+
243
+ if (count === 1) {
244
+ const result = await pingOnce(rpcUrl);
245
+ printResult(result, asJson);
246
+ if (!asJson) console.log();
247
+ // Exit 1 if unreachable
248
+ if (result.error) process.exit(1);
249
+ } else {
250
+ const results = await pingMulti(rpcUrl, count);
251
+ printAggregated(results, rpcUrl, asJson);
252
+ // Exit 1 if all failed
253
+ if (results.every(r => r.error)) process.exit(1);
254
+ }
255
+ }
256
+
257
+ main().catch(err => {
258
+ console.error(`\n${C.red}✗ Ping failed:${C.reset} ${err.message}\n`);
259
+ process.exit(1);
260
+ });
261
+
262
+ module.exports = { pingCommand: main };
263
+
264
+ if (require.main === module) {
265
+ main();
266
+ }