@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.
package/lib/ui.js ADDED
@@ -0,0 +1,623 @@
1
+ /**
2
+ * aether-cli/lib/ui.js
3
+ *
4
+ * Unified UI/UX Framework for Aether CLI
5
+ *
6
+ * Provides:
7
+ * - Consistent ASCII art branding
8
+ * - Standardized color palette
9
+ * - Status indicators and spinners
10
+ * - Progress feedback helpers
11
+ * - Box drawing utilities
12
+ * - Table formatting
13
+ */
14
+
15
+ // ============================================================================
16
+ // ANSI Color Palette - Consistent across all commands
17
+ // ============================================================================
18
+
19
+ const colors = {
20
+ // Standard colors
21
+ reset: '\x1b[0m',
22
+ bright: '\x1b[1m',
23
+ dim: '\x1b[2m',
24
+ underscore: '\x1b[4m',
25
+
26
+ // Foreground colors
27
+ black: '\x1b[30m',
28
+ red: '\x1b[31m',
29
+ green: '\x1b[32m',
30
+ yellow: '\x1b[33m',
31
+ blue: '\x1b[34m',
32
+ magenta: '\x1b[35m',
33
+ cyan: '\x1b[36m',
34
+ white: '\x1b[37m',
35
+
36
+ // Bright variants
37
+ brightRed: '\x1b[91m',
38
+ brightGreen: '\x1b[92m',
39
+ brightYellow: '\x1b[93m',
40
+ brightBlue: '\x1b[94m',
41
+ brightMagenta: '\x1b[95m',
42
+ brightCyan: '\x1b[96m',
43
+ brightWhite: '\x1b[97m',
44
+
45
+ // Background colors
46
+ bgBlack: '\x1b[40m',
47
+ bgRed: '\x1b[41m',
48
+ bgGreen: '\x1b[42m',
49
+ bgYellow: '\x1b[43m',
50
+ bgBlue: '\x1b[44m',
51
+ bgMagenta: '\x1b[45m',
52
+ bgCyan: '\x1b[46m',
53
+ bgWhite: '\x1b[47m',
54
+ };
55
+
56
+ // Short aliases for convenience
57
+ const C = colors;
58
+
59
+ // ============================================================================
60
+ // ASCII Art Branding
61
+ // ============================================================================
62
+
63
+ const BRANDING = {
64
+ // Main Aether logo - cosmic blockchain aesthetic
65
+ logo: `
66
+ ${C.cyan} ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ${C.reset}
67
+ ${C.cyan} ▄▀░░░░░░░░░░░░░░░░░░░░░░░▀▄ ${C.reset}
68
+ ${C.cyan} ▄▀░░░░░░░░░${C.bright}◆${C.cyan}░░░░░░░░░░░░░░░░░▀▄ ${C.reset}
69
+ ${C.cyan} █░░░░░${C.blue}╔═╗╔═╗╔╦╗╔═╗╔═╗╦═╗${C.cyan}░░░░░░░█ ${C.reset}
70
+ ${C.cyan} █░░░░░░${C.blue}╠═╣╠╦╝║║║║ ╦║╣ ╠╦╝${C.cyan}░░░░░░░░█ ${C.reset}
71
+ ${C.cyan} █░░░░░░${C.blue}╩ ╩╩╚═╩ ╩╚═╝╚═╝╩╚═${C.cyan}░░░░░░░░█ ${C.reset}
72
+ ${C.cyan} █░░░░░░░░░${C.bright}◆${C.cyan}░░░░░░░░░░░░░░░░░█ ${C.reset}
73
+ ${C.cyan} ▀▄░░░░░░░░░░░░░░░░░░░░░▄▀ ${C.reset}
74
+ ${C.cyan} ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ${C.reset}`,
75
+
76
+ // Compact logo for tight spaces
77
+ logoCompact: `
78
+ ${C.cyan} ╔═╗╔═╗╔╦╗╔═╗╔═╗╦═╗${C.reset}
79
+ ${C.cyan} ╠═╣╠╦╝║║║║ ╦║╣ ╠╦╝${C.reset}
80
+ ${C.cyan} ╩ ╩╩╚═╩ ╩╚═╝╚═╝╩╚═${C.reset}`,
81
+
82
+ // Validator node branding
83
+ validatorLogo: `
84
+ ${C.cyan} ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄ ${C.reset}
85
+ ${C.cyan} █ ${C.bright}${C.cyan}AETHER${C.reset} ${C.bright}VALIDATOR NODE${C.reset} █ ${C.reset}
86
+ ${C.cyan} █ █ ${C.reset}
87
+ ${C.cyan} ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀ ${C.reset}`,
88
+
89
+ // CLI header with version - cosmic theme
90
+ header: (version) => `
91
+ ${C.cyan}╔══════════════════════════════════════════════════════════════════════════╗${C.reset}
92
+ ${C.cyan}║${C.reset} ${C.cyan}║${C.reset}
93
+ ${C.cyan}║${C.reset} ${C.cyan}╔═╗${C.bright}${C.cyan}AETHER${C.reset}${C.cyan}╔═╗${C.reset} ${C.bright}BLOCKCHAIN CLI${C.reset}${' '.repeat(34 - version.length)}${C.dim}v${version}${C.reset} ${C.cyan}║${C.reset}
94
+ ${C.cyan}║${C.reset} ${C.cyan}╠═╣${C.bright}${C.cyan}NETWORK${C.reset}${C.cyan}╠═╣${C.reset} ${C.dim}◆ Decentralized Infrastructure for the Future ◆${C.reset} ${C.cyan}║${C.reset}
95
+ ${C.cyan}║${C.reset} ${C.cyan}╚═╝${C.reset}${' '.repeat(10)}${C.cyan}╚═╝${C.reset} ${C.cyan}║${C.reset}
96
+ ${C.cyan}║${C.reset} ${C.cyan}║${C.reset}
97
+ ${C.cyan}╚══════════════════════════════════════════════════════════════════════════╝${C.reset}`,
98
+
99
+ // Subtle header without borders
100
+ headerMinimal: (version) => `
101
+ ${C.cyan} ◆ AETHER CLI${C.reset} ${C.dim}v${version}${C.reset} ${C.dim}─ Decentralized Infrastructure ◆${C.reset}
102
+ `,
103
+
104
+ // Section headers - cosmic dividers
105
+ section: (title) => `
106
+ ${C.cyan} ════════════════════════════════════════════════════════════════════${C.reset}
107
+ ${C.cyan} ◆${C.reset} ${C.bright}${title.toUpperCase()}${C.reset}
108
+ ${C.cyan} ────────────────────────────────────────────────────────────────────${C.reset}`,
109
+
110
+ // Subsection
111
+ subsection: (title) => `
112
+ ${C.dim}┌─ ${C.bright}${C.cyan}${title}${C.reset}${C.dim} ─────────────────────────────────────────┐${C.reset}`,
113
+
114
+ // Command banner
115
+ commandBanner: (cmd, desc) => `
116
+ ${C.cyan}┌──────────────────────────────────────────────────────────────────────────┐${C.reset}
117
+ ${C.cyan}│${C.reset} ${C.bright}${C.cyan}COMMAND:${C.reset} ${C.bright}${cmd}${C.reset}${' '.repeat(60 - cmd.length)}${C.cyan}│${C.reset}
118
+ ${C.cyan}│${C.reset} ${C.dim}${desc}${C.reset}${' '.repeat(66 - desc.length)}${C.cyan}│${C.reset}
119
+ ${C.cyan}└──────────────────────────────────────────────────────────────────────────┘${C.reset}`,
120
+
121
+ // Welcome banner for init
122
+ welcomeBanner: `
123
+ ${C.cyan}╔══════════════════════════════════════════════════════════════════════════╗${C.reset}
124
+ ${C.cyan}║${C.reset} ${C.cyan}║${C.reset}
125
+ ${C.cyan}║${C.reset} ${C.cyan}╔═╗${C.bright}${C.cyan}WELCOME TO AETHER${C.reset}${C.cyan}╔═╗${C.reset}${' '.repeat(39)}${C.cyan}║${C.reset}
126
+ ${C.cyan}║${C.reset} ${C.cyan}╚═╝${C.reset} ${C.dim}Validator Onboarding & Node Management${C.reset} ${C.cyan}║${C.reset}
127
+ ${C.cyan}║${C.reset} ${C.cyan}║${C.reset}
128
+ ${C.cyan}╚══════════════════════════════════════════════════════════════════════════╝${C.reset}`,
129
+
130
+ // Success banner
131
+ successBanner: (text) => `
132
+ ${C.cyan}╔══════════════════════════════════════════════════════════════════════════╗${C.reset}
133
+ ${C.cyan}║${C.reset} ${C.green}${C.bright}✓ ${text}${C.reset}${' '.repeat(66 - text.length)}${C.cyan}║${C.reset}
134
+ ${C.cyan}╚══════════════════════════════════════════════════════════════════════════╝${C.reset}`,
135
+
136
+ // Error banner
137
+ errorBanner: (text) => `
138
+ ${C.cyan}╔══════════════════════════════════════════════════════════════════════════╗${C.reset}
139
+ ${C.cyan}║${C.reset} ${C.red}${C.bright}✗ ${text}${C.reset}${' '.repeat(66 - text.length)}${C.cyan}║${C.reset}
140
+ ${C.cyan}╚══════════════════════════════════════════════════════════════════════════╝${C.reset}`,
141
+ };
142
+
143
+ // ============================================================================
144
+ // Status Indicators - Consistent across all commands
145
+ // ============================================================================
146
+
147
+ const indicators = {
148
+ // Success states
149
+ success: `${C.green}✓${C.reset}`,
150
+ successBright: `${C.green}${C.bright}✓${C.reset}`,
151
+ successBox: `${C.green}[✓]${C.reset}`,
152
+
153
+ // Error states
154
+ error: `${C.red}✗${C.reset}`,
155
+ errorBright: `${C.red}${C.bright}✗${C.reset}`,
156
+ errorBox: `${C.red}[✗]${C.reset}`,
157
+
158
+ // Warning states
159
+ warning: `${C.yellow}⚠${C.reset}`,
160
+ warningBright: `${C.yellow}${C.bright}⚠${C.reset}`,
161
+ warningBox: `${C.yellow}[⚠]${C.reset}`,
162
+
163
+ // Info states
164
+ info: `${C.cyan}ℹ${C.reset}`,
165
+ infoBright: `${C.cyan}${C.bright}ℹ${C.reset}`,
166
+ infoBox: `${C.cyan}[ℹ]${C.reset}`,
167
+
168
+ // Progress/loading
169
+ bullet: `${C.cyan}●${C.reset}`,
170
+ bulletDim: `${C.dim}●${C.reset}`,
171
+ arrow: `${C.cyan}→${C.reset}`,
172
+ arrowRight: `${C.cyan}→${C.reset}`,
173
+ arrowLeft: `${C.cyan}←${C.reset}`,
174
+
175
+ // Network/connection
176
+ connected: `${C.green}●${C.reset}`,
177
+ disconnected: `${C.red}●${C.reset}`,
178
+ syncing: `${C.yellow}◐${C.reset}`,
179
+
180
+ // Checkboxes
181
+ checked: `${C.green}[✓]${C.reset}`,
182
+ unchecked: `${C.dim}[ ]${C.reset}`,
183
+
184
+ // Stars/ratings
185
+ star: `${C.yellow}★${C.reset}`,
186
+ starDim: `${C.dim}★${C.reset}`,
187
+ };
188
+
189
+ // ============================================================================
190
+ // Box Drawing - Consistent borders
191
+ // ============================================================================
192
+
193
+ const box = {
194
+ // Single line borders
195
+ single: {
196
+ topLeft: '┌', topRight: '┐', bottomLeft: '└', bottomRight: '┘',
197
+ horizontal: '─', vertical: '│',
198
+ leftT: '├', rightT: '┤', topT: '┬', bottomT: '┴', cross: '┼'
199
+ },
200
+
201
+ // Double line borders
202
+ double: {
203
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
204
+ horizontal: '═', vertical: '║',
205
+ leftT: '╠', rightT: '╣', topT: '╦', bottomT: '╩', cross: '╬'
206
+ },
207
+
208
+ // Rounded corners
209
+ rounded: {
210
+ topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
211
+ horizontal: '─', vertical: '│'
212
+ },
213
+
214
+ // Thick borders
215
+ thick: {
216
+ topLeft: '▛', topRight: '▜', bottomLeft: '▙', bottomRight: '▟',
217
+ horizontal: '━', vertical: '┃'
218
+ }
219
+ };
220
+
221
+ // ============================================================================
222
+ // Message Helpers
223
+ // ============================================================================
224
+
225
+ function success(text) {
226
+ return `${indicators.success} ${text}`;
227
+ }
228
+
229
+ function error(text) {
230
+ return `${indicators.error} ${text}`;
231
+ }
232
+
233
+ function warning(text) {
234
+ return `${indicators.warning} ${text}`;
235
+ }
236
+
237
+ function info(text) {
238
+ return `${indicators.info} ${text}`;
239
+ }
240
+
241
+ function dim(text) {
242
+ return `${C.dim}${text}${C.reset}`;
243
+ }
244
+
245
+ function bright(text) {
246
+ return `${C.bright}${text}${C.reset}`;
247
+ }
248
+
249
+ function highlight(text) {
250
+ return `${C.cyan}${C.bright}${text}${C.reset}`;
251
+ }
252
+
253
+ function code(text) {
254
+ return `${C.cyan}${text}${C.reset}`;
255
+ }
256
+
257
+ function key(text) {
258
+ return `${C.yellow}${C.bright}${text}${C.reset}`;
259
+ }
260
+
261
+ function value(text) {
262
+ return `${C.green}${text}${C.reset}`;
263
+ }
264
+
265
+ // ============================================================================
266
+ // Progress/Spinner Helpers
267
+ // ============================================================================
268
+
269
+ const spinnerFrames = ['◐', '◓', '◑', '◒'];
270
+ let spinnerInterval = null;
271
+ let spinnerIndex = 0;
272
+
273
+ function startSpinner(text = 'Loading') {
274
+ if (spinnerInterval) clearSpinner();
275
+
276
+ spinnerIndex = 0;
277
+ process.stdout.write(` ${C.dim}${spinnerFrames[0]}${C.reset} ${text}...`);
278
+
279
+ spinnerInterval = setInterval(() => {
280
+ spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
281
+ process.stdout.write(`\r ${C.cyan}${spinnerFrames[spinnerIndex]}${C.reset} ${text}...`);
282
+ }, 120);
283
+ }
284
+
285
+ function updateSpinner(text) {
286
+ if (spinnerInterval) {
287
+ process.stdout.write(`\r ${C.cyan}${spinnerFrames[spinnerIndex]}${C.reset} ${text}...`);
288
+ }
289
+ }
290
+
291
+ function stopSpinner(success = true, finalText = null) {
292
+ if (spinnerInterval) {
293
+ clearInterval(spinnerInterval);
294
+ spinnerInterval = null;
295
+ }
296
+
297
+ const icon = success ? indicators.success : indicators.error;
298
+ const text = finalText || (success ? 'Done' : 'Failed');
299
+ process.stdout.write(`\r ${icon} ${text}\n`);
300
+ }
301
+
302
+ function clearSpinner() {
303
+ if (spinnerInterval) {
304
+ clearInterval(spinnerInterval);
305
+ spinnerInterval = null;
306
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
307
+ }
308
+ }
309
+
310
+ // ============================================================================
311
+ // Progress Bar
312
+ // ============================================================================
313
+
314
+ function progressBar(current, total, width = 40) {
315
+ const pct = Math.min(100, Math.max(0, (current / total) * 100));
316
+ const filled = Math.round((pct / 100) * width);
317
+ const empty = width - filled;
318
+
319
+ const filledBar = C.green + '█'.repeat(filled) + C.reset;
320
+ const emptyBar = C.dim + '░'.repeat(empty) + C.reset;
321
+
322
+ return `[${filledBar}${emptyBar}] ${C.bright}${pct.toFixed(1)}%${C.reset}`;
323
+ }
324
+
325
+ function progressBarColored(current, total, width = 40) {
326
+ const pct = Math.min(100, Math.max(0, (current / total) * 100));
327
+ const filled = Math.round((pct / 100) * width);
328
+ const empty = width - filled;
329
+
330
+ // Color based on progress
331
+ let color = C.red;
332
+ if (pct > 30) color = C.yellow;
333
+ if (pct > 70) color = C.green;
334
+
335
+ const filledBar = color + '█'.repeat(filled) + C.reset;
336
+ const emptyBar = C.dim + '░'.repeat(empty) + C.reset;
337
+
338
+ return `[${filledBar}${emptyBar}] ${C.bright}${pct.toFixed(1)}%${C.reset}`;
339
+ }
340
+
341
+ // ============================================================================
342
+ // Box Drawing Functions
343
+ // ============================================================================
344
+
345
+ function drawBox(content, options = {}) {
346
+ const {
347
+ style = 'single',
348
+ padding = 1,
349
+ width = null,
350
+ title = null,
351
+ titleColor = C.cyan,
352
+ borderColor = C.dim,
353
+ align = 'left'
354
+ } = options;
355
+
356
+ const lines = content.split('\n');
357
+ const maxWidth = width || Math.max(...lines.map(l => stripAnsi(l).length));
358
+ const b = box[style];
359
+
360
+ let result = '';
361
+
362
+ // Top border with optional title
363
+ if (title) {
364
+ const titleText = ` ${title} `;
365
+ const sideWidth = Math.max(0, (maxWidth + padding * 2 - stripAnsi(titleText).length) / 2);
366
+ const leftPad = b.horizontal.repeat(Math.floor(sideWidth));
367
+ const rightPad = b.horizontal.repeat(Math.ceil(sideWidth));
368
+ result += `${borderColor}${b.topLeft}${leftPad}${titleColor}${titleText}${borderColor}${rightPad}${b.topRight}${C.reset}\n`;
369
+ } else {
370
+ result += `${borderColor}${b.topLeft}${b.horizontal.repeat(maxWidth + padding * 2)}${b.topRight}${C.reset}\n`;
371
+ }
372
+
373
+ // Content lines
374
+ for (const line of lines) {
375
+ const stripped = stripAnsi(line);
376
+ const padLeft = ' '.repeat(padding);
377
+ const padRight = ' '.repeat(maxWidth - stripped.length + padding);
378
+ result += `${borderColor}${b.vertical}${C.reset}${padLeft}${line}${padRight}${borderColor}${b.vertical}${C.reset}\n`;
379
+ }
380
+
381
+ // Bottom border
382
+ result += `${borderColor}${b.bottomLeft}${b.horizontal.repeat(maxWidth + padding * 2)}${b.bottomRight}${C.reset}`;
383
+
384
+ return result;
385
+ }
386
+
387
+ function drawTable(headers, rows, options = {}) {
388
+ const {
389
+ borderStyle = 'single',
390
+ headerColor = C.cyan + C.bright,
391
+ borderColor = C.dim,
392
+ cellPadding = 1
393
+ } = options;
394
+
395
+ // Calculate column widths
396
+ const colWidths = headers.map((h, i) => {
397
+ const headerWidth = stripAnsi(h).length;
398
+ const maxDataWidth = Math.max(...rows.map(r => stripAnsi(String(r[i] || '')).length));
399
+ return Math.max(headerWidth, maxDataWidth) + cellPadding * 2;
400
+ });
401
+
402
+ const b = box[borderStyle];
403
+ let result = '';
404
+
405
+ // Top border
406
+ const topLine = colWidths.map(w => b.horizontal.repeat(w)).join(b.topT);
407
+ result += `${borderColor}${b.topLeft}${topLine}${b.topRight}${C.reset}\n`;
408
+
409
+ // Header row
410
+ const headerLine = headers.map((h, i) => {
411
+ const pad = colWidths[i] - stripAnsi(h).length;
412
+ const left = Math.floor(pad / 2);
413
+ const right = pad - left;
414
+ return ' '.repeat(left) + headerColor + h + C.reset + ' '.repeat(right);
415
+ }).join(borderColor + b.vertical + C.reset);
416
+ result += `${borderColor}${b.vertical}${C.reset}${headerLine}${borderColor}${b.vertical}${C.reset}\n`;
417
+
418
+ // Separator
419
+ const sepLine = colWidths.map(w => b.horizontal.repeat(w)).join(b.cross);
420
+ result += `${borderColor}${b.leftT}${sepLine}${b.rightT}${C.reset}\n`;
421
+
422
+ // Data rows
423
+ for (const row of rows) {
424
+ const line = row.map((cell, i) => {
425
+ const text = String(cell || '');
426
+ const pad = colWidths[i] - stripAnsi(text).length;
427
+ const left = cellPadding;
428
+ const right = pad - left;
429
+ return ' '.repeat(left) + text + ' '.repeat(right);
430
+ }).join(borderColor + b.vertical + C.reset);
431
+ result += `${borderColor}${b.vertical}${C.reset}${line}${borderColor}${b.vertical}${C.reset}\n`;
432
+ }
433
+
434
+ // Bottom border
435
+ const bottomLine = colWidths.map(w => b.horizontal.repeat(w)).join(b.bottomT);
436
+ result += `${borderColor}${b.bottomLeft}${bottomLine}${b.bottomRight}${C.reset}`;
437
+
438
+ return result;
439
+ }
440
+
441
+ // ============================================================================
442
+ // Utility Functions
443
+ // ============================================================================
444
+
445
+ function stripAnsi(str) {
446
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
447
+ }
448
+
449
+ function center(text, width) {
450
+ const stripped = stripAnsi(text);
451
+ const pad = Math.max(0, width - stripped.length);
452
+ const left = Math.floor(pad / 2);
453
+ const right = pad - left;
454
+ return ' '.repeat(left) + text + ' '.repeat(right);
455
+ }
456
+
457
+ function pad(text, width, align = 'left') {
458
+ const stripped = stripAnsi(text);
459
+ const padLen = Math.max(0, width - stripped.length);
460
+
461
+ if (align === 'right') return ' '.repeat(padLen) + text;
462
+ if (align === 'center') {
463
+ const left = Math.floor(padLen / 2);
464
+ const right = padLen - left;
465
+ return ' '.repeat(left) + text + ' '.repeat(right);
466
+ }
467
+ return text + ' '.repeat(padLen);
468
+ }
469
+
470
+ function truncate(text, maxLen, suffix = '...') {
471
+ const stripped = stripAnsi(text);
472
+ if (stripped.length <= maxLen) return text;
473
+ return stripped.slice(0, maxLen - suffix.length) + suffix;
474
+ }
475
+
476
+ function wrap(text, width) {
477
+ const words = text.split(' ');
478
+ const lines = [];
479
+ let currentLine = '';
480
+
481
+ for (const word of words) {
482
+ if (stripAnsi(currentLine + ' ' + word).length > width) {
483
+ lines.push(currentLine);
484
+ currentLine = word;
485
+ } else {
486
+ currentLine += (currentLine ? ' ' : '') + word;
487
+ }
488
+ }
489
+ if (currentLine) lines.push(currentLine);
490
+
491
+ return lines;
492
+ }
493
+
494
+ // ============================================================================
495
+ // Help Formatting
496
+ // ============================================================================
497
+
498
+ function formatHelp(title, description, usage, options, examples) {
499
+ let result = '';
500
+
501
+ // Title
502
+ result += `\n${C.cyan}${C.bright}${title}${C.reset}\n`;
503
+
504
+ // Description
505
+ if (description) {
506
+ result += `\n${C.dim}${description}${C.reset}\n`;
507
+ }
508
+
509
+ // Usage
510
+ if (usage) {
511
+ result += `\n${C.bright}USAGE${C.reset}\n`;
512
+ result += ` ${C.cyan}${usage}${C.reset}\n`;
513
+ }
514
+
515
+ // Options
516
+ if (options && options.length > 0) {
517
+ result += `\n${C.bright}OPTIONS${C.reset}\n`;
518
+ const maxFlagLen = Math.max(...options.map(o => stripAnsi(o.flag).length));
519
+ for (const opt of options) {
520
+ result += ` ${C.cyan}${pad(opt.flag, maxFlagLen)}${C.reset} ${opt.desc}\n`;
521
+ }
522
+ }
523
+
524
+ // Examples
525
+ if (examples && examples.length > 0) {
526
+ result += `\n${C.bright}EXAMPLES${C.reset}\n`;
527
+ for (const ex of examples) {
528
+ result += ` ${C.dim}$${C.reset} ${C.cyan}${ex.cmd}${C.reset}\n`;
529
+ if (ex.desc) {
530
+ result += ` ${C.dim}${' '.repeat(ex.cmd.length + 1)}${ex.desc}${C.reset}\n`;
531
+ }
532
+ }
533
+ }
534
+
535
+ result += '\n';
536
+ return result;
537
+ }
538
+
539
+ // ============================================================================
540
+ // Network Status Helpers
541
+ // ============================================================================
542
+
543
+ function formatLatency(ms) {
544
+ if (ms < 50) return `${C.green}${ms}ms${C.reset}`;
545
+ if (ms < 150) return `${C.yellow}${ms}ms${C.reset}`;
546
+ return `${C.red}${ms}ms${C.reset}`;
547
+ }
548
+
549
+ function formatHealth(status) {
550
+ if (!status) return `${C.dim}unknown${C.reset}`;
551
+ const s = status.toLowerCase();
552
+ if (s === 'ok' || s === 'healthy') return `${C.green}●${C.reset} ${C.green}${status}${C.reset}`;
553
+ if (s === 'degraded') return `${C.yellow}●${C.reset} ${C.yellow}${status}${C.reset}`;
554
+ return `${C.red}●${C.reset} ${C.red}${status}${C.reset}`;
555
+ }
556
+
557
+ function formatSyncStatus(currentSlot, targetSlot) {
558
+ if (!currentSlot || !targetSlot) return `${C.dim}unknown${C.reset}`;
559
+ const pct = (currentSlot / targetSlot) * 100;
560
+ const behind = targetSlot - currentSlot;
561
+
562
+ if (pct >= 99.9) return `${C.green}synced${C.reset}`;
563
+ if (pct >= 95) return `${C.yellow}syncing${C.reset} ${C.dim}(${behind} behind)${C.reset}`;
564
+ return `${C.red}syncing${C.reset} ${C.dim}(${behind} behind)${C.reset}`;
565
+ }
566
+
567
+ // ============================================================================
568
+ // Export
569
+ // ============================================================================
570
+
571
+ module.exports = {
572
+ // Colors
573
+ colors,
574
+ C,
575
+
576
+ // Branding
577
+ BRANDING,
578
+
579
+ // Indicators
580
+ indicators,
581
+
582
+ // Box characters
583
+ box,
584
+
585
+ // Message helpers
586
+ success,
587
+ error,
588
+ warning,
589
+ info,
590
+ dim,
591
+ bright,
592
+ highlight,
593
+ code,
594
+ key,
595
+ value,
596
+
597
+ // Spinner/progress
598
+ startSpinner,
599
+ updateSpinner,
600
+ stopSpinner,
601
+ clearSpinner,
602
+ progressBar,
603
+ progressBarColored,
604
+
605
+ // Box drawing
606
+ drawBox,
607
+ drawTable,
608
+
609
+ // Utilities
610
+ stripAnsi,
611
+ center,
612
+ pad,
613
+ truncate,
614
+ wrap,
615
+
616
+ // Help formatting
617
+ formatHelp,
618
+
619
+ // Network helpers
620
+ formatLatency,
621
+ formatHealth,
622
+ formatSyncStatus,
623
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jellylegsai/aether-cli",
3
- "version": "1.9.2",
3
+ "version": "2.0.1",
4
4
  "description": "Aether CLI - Validator Onboarding, Staking, and Blockchain Operations",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  "aether": "index.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node test.js && cd sdk && node test.js",
11
+ "build": "echo 'CLI package requires no build step'",
12
+ "test": "cd test && node doctor.test.js && cd ../sdk && node test.js",
12
13
  "lint": "echo 'No linting configured'",
13
14
  "doctor": "node index.js doctor",
14
15
  "init": "node index.js init",
@@ -35,6 +36,7 @@
35
36
  "tx-history": "node index.js tx-history",
36
37
  "tx": "node index.js tx-history",
37
38
  "network": "node index.js network",
39
+ "network-diagnostics": "node index.js network-diagnostics",
38
40
  "epoch": "node index.js epoch",
39
41
  "supply": "node index.js supply",
40
42
  "status": "node index.js status",
@@ -47,6 +49,11 @@
47
49
  "emergency": "node index.js emergency",
48
50
  "snapshot": "node index.js snapshot",
49
51
  "nft": "node index.js nft",
52
+ "deploy": "node index.js deploy",
53
+ "call": "node index.js call",
54
+ "blockheight": "node index.js blockheight",
55
+ "token-accounts": "node index.js token-accounts",
56
+ "version": "node index.js version",
50
57
  "prepare-publish": "npm pack && echo 'Package ready for publishing'",
51
58
  "version-bump": "node -e \"const fs=require('fs');let p=JSON.parse(fs.readFileSync('package.json'));let v=p.version.split('.');v[2]=parseInt(v[2])+1;p.version=v.join('.');fs.writeFileSync('package.json',JSON.stringify(p,null,2));console.log('Version bumped to',p.version);\""
52
59
  },
@@ -73,4 +80,4 @@
73
80
  "tweetnacl": "^1.0.3",
74
81
  "bs58": "^5.0.0"
75
82
  }
76
- }
83
+ }