aether-hub 1.2.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/commands/ping.js CHANGED
@@ -1,266 +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
- * 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
- }
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
+ }