@jellylegsai/aether-cli 1.8.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/aether-cli-1.0.0.tgz +0 -0
  4. package/aether-cli-1.8.0.tgz +0 -0
  5. package/aether-hub-1.0.5.tgz +0 -0
  6. package/aether-hub-1.1.8.tgz +0 -0
  7. package/aether-hub-1.2.1.tgz +0 -0
  8. package/commands/account.js +280 -0
  9. package/commands/apy.js +499 -0
  10. package/commands/balance.js +241 -0
  11. package/commands/blockhash.js +181 -0
  12. package/commands/broadcast.js +387 -0
  13. package/commands/claim.js +490 -0
  14. package/commands/config.js +851 -0
  15. package/commands/delegations.js +582 -0
  16. package/commands/doctor.js +769 -0
  17. package/commands/emergency.js +667 -0
  18. package/commands/epoch.js +275 -0
  19. package/commands/fees.js +276 -0
  20. package/commands/index.js +78 -0
  21. package/commands/info.js +495 -0
  22. package/commands/init.js +816 -0
  23. package/commands/install.js +666 -0
  24. package/commands/kyc.js +272 -0
  25. package/commands/logs.js +315 -0
  26. package/commands/monitor.js +431 -0
  27. package/commands/multisig.js +701 -0
  28. package/commands/network.js +429 -0
  29. package/commands/nft.js +857 -0
  30. package/commands/ping.js +266 -0
  31. package/commands/price.js +253 -0
  32. package/commands/rewards.js +931 -0
  33. package/commands/sdk-test.js +477 -0
  34. package/commands/sdk.js +656 -0
  35. package/commands/slot.js +155 -0
  36. package/commands/snapshot.js +470 -0
  37. package/commands/stake-info.js +139 -0
  38. package/commands/stake-positions.js +205 -0
  39. package/commands/stake.js +516 -0
  40. package/commands/stats.js +396 -0
  41. package/commands/status.js +327 -0
  42. package/commands/supply.js +391 -0
  43. package/commands/tps.js +238 -0
  44. package/commands/transfer.js +495 -0
  45. package/commands/tx-history.js +346 -0
  46. package/commands/unstake.js +597 -0
  47. package/commands/validator-info.js +657 -0
  48. package/commands/validator-register.js +593 -0
  49. package/commands/validator-start.js +323 -0
  50. package/commands/validator-status.js +227 -0
  51. package/commands/validators.js +626 -0
  52. package/commands/wallet.js +1570 -0
  53. package/index.js +593 -0
  54. package/lib/errors.js +398 -0
  55. package/package.json +76 -0
  56. package/sdk/README.md +210 -0
  57. package/sdk/index.js +1639 -0
  58. package/sdk/package.json +34 -0
  59. package/sdk/rpc.js +254 -0
  60. package/sdk/test.js +85 -0
  61. package/test/doctor.test.js +76 -0
  62. package/validator-identity.json +4 -0
@@ -0,0 +1,666 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli installer
4
+ *
5
+ * One-command installer for aether-cli across platforms.
6
+ * Handles: npm install, config init, environment checks, path setup.
7
+ *
8
+ * Usage:
9
+ * curl -sSL https://get.aether.network/install.sh | bash
10
+ * # or
11
+ * npm install -g @jellylegsai/aether-cli
12
+ * aether-cli install
13
+ * aether-cli install --path ~/.local/bin
14
+ * aether-cli install --rpc https://my-node:8899
15
+ * aether-cli install --skip-rpc-check
16
+ * aether-cli install --uninstall
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('os');
22
+ const { execSync, spawn } = require('child_process');
23
+ const readline = require('readline');
24
+ const https = require('https');
25
+ const http = require('http');
26
+
27
+ // ANSI colours
28
+ const C = {
29
+ reset: '\x1b[0m',
30
+ bright: '\x1b[1m',
31
+ dim: '\x1b[2m',
32
+ red: '\x1b[31m',
33
+ green: '\x1b[32m',
34
+ yellow: '\x1b[33m',
35
+ cyan: '\x1b[36m',
36
+ magenta: '\x1b[35m',
37
+ };
38
+
39
+ // Installer version
40
+ const VERSION = '1.0.0';
41
+
42
+ // Detect platform
43
+ const isWindows = os.platform() === 'win32';
44
+ const isMac = os.platform() === 'darwin';
45
+ const isLinux = os.platform() === 'linux';
46
+ const platform = isWindows ? 'windows' : isMac ? 'macos' : isLinux ? 'linux' : 'unknown';
47
+
48
+ // ============================================================================
49
+ // Output Helpers
50
+ // ============================================================================
51
+
52
+ function log(msg, color = C.reset) {
53
+ console.log(`${color}${msg}${C.reset}`);
54
+ }
55
+
56
+ function info(msg) { log(` ℹ ${msg}`, C.cyan); }
57
+ function ok(msg) { log(` ✓ ${msg}`, C.green); }
58
+ function warn(msg) { log(` ⚠ ${msg}`, C.yellow); }
59
+ function error(msg) { log(` ✗ ${msg}`, C.red); }
60
+ function step(msg) { log(` ▶ ${msg}`, C.magenta); }
61
+ function header(msg) { log(`\n ${C.bright}${C.cyan}${msg}${C.reset}`); }
62
+ function sub(msg) { log(` ${C.dim}${msg}${C.reset}`); }
63
+
64
+ function separator() {
65
+ log(` ${C.dim}${'─'.repeat(60)}${C.reset}`);
66
+ }
67
+
68
+ // ============================================================================
69
+ // Environment Detection
70
+ // ============================================================================
71
+
72
+ function getNodeVersion() {
73
+ try {
74
+ return process.version.replace('v', '');
75
+ } catch {
76
+ return 'unknown';
77
+ }
78
+ }
79
+
80
+ function getNpmVersion() {
81
+ try {
82
+ return execSync('npm --version', { encoding: 'utf8' }).trim();
83
+ } catch {
84
+ return 'not found';
85
+ }
86
+ }
87
+
88
+ function getInstallPath() {
89
+ try {
90
+ const globalPath = execSync('npm root -g', { encoding: 'utf8' }).trim();
91
+ return globalPath;
92
+ } catch {
93
+ return path.join(os.homedir(), '.npm-global', 'lib', 'node_modules');
94
+ }
95
+ }
96
+
97
+ function getAetherDir() {
98
+ return path.join(os.homedir(), '.aether');
99
+ }
100
+
101
+ function getConfigPath() {
102
+ return path.join(getAetherDir(), 'config.json');
103
+ }
104
+
105
+ function checkExistingInstall() {
106
+ try {
107
+ const result = execSync('npm list -g @jellylegsai/aether-cli --depth=0 2>nul', { encoding: 'utf8', shell: true });
108
+ if (result.includes('@jellylegsai/aether-cli')) {
109
+ const match = result.match(/@jellylegsai\/aether-cli@([\d.]+)/);
110
+ return { installed: true, version: match ? match[1] : 'unknown' };
111
+ }
112
+ } catch {
113
+ // Not found via npm
114
+ }
115
+
116
+ // Check if CLI is in PATH
117
+ const cliPath = which('aether') || which('aether-cli');
118
+ if (cliPath) {
119
+ return { installed: true, version: 'unknown', path: cliPath };
120
+ }
121
+
122
+ return { installed: false };
123
+ }
124
+
125
+ function which(cmd) {
126
+ try {
127
+ const result = execSync(isWindows ? `where ${cmd}` : `which ${cmd}`, { encoding: 'utf8' });
128
+ return result.split('\n')[0].trim();
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ // ============================================================================
135
+ // RPC Validation
136
+ // ============================================================================
137
+
138
+ function validateRpcUrl(url, timeoutMs = 5000) {
139
+ return new Promise((resolve) => {
140
+ try {
141
+ const urlObj = new URL(url);
142
+ const lib = urlObj.protocol === 'https:' ? https : http;
143
+
144
+ const req = lib.request({
145
+ hostname: urlObj.hostname,
146
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
147
+ path: '/v1/health',
148
+ method: 'GET',
149
+ timeout: timeoutMs,
150
+ headers: { 'Content-Type': 'application/json' },
151
+ }, (res) => {
152
+ let data = '';
153
+ res.on('data', (chunk) => data += chunk);
154
+ res.on('end', () => {
155
+ try {
156
+ const parsed = JSON.parse(data);
157
+ resolve({ valid: true, url, latency: 0, status: parsed.status || 'ok' });
158
+ } catch {
159
+ resolve({ valid: true, url, latency: 0, status: 'ok' });
160
+ }
161
+ });
162
+ });
163
+
164
+ const start = Date.now();
165
+ req.on('response', () => {
166
+ const latency = Date.now() - start;
167
+ resolve({ valid: true, url, latency, status: 'ok' });
168
+ });
169
+ req.on('error', (err) => {
170
+ resolve({ valid: false, url, error: err.message });
171
+ });
172
+ req.on('timeout', () => {
173
+ req.destroy();
174
+ resolve({ valid: false, url, error: 'timeout' });
175
+ });
176
+ req.end();
177
+ } catch (err) {
178
+ resolve({ valid: false, url, error: err.message });
179
+ }
180
+ });
181
+ }
182
+
183
+ // ============================================================================
184
+ // Installation Steps
185
+ // ============================================================================
186
+
187
+ async function stepPreInstall(args) {
188
+ header('PRE-INSTALLATION CHECKS');
189
+
190
+ // Check Node.js version
191
+ const nodeVersion = getNodeVersion();
192
+ const nodeMajor = parseInt(nodeVersion.split('.')[0].replace('v', ''), 10);
193
+ info(`Node.js version: ${nodeVersion}`);
194
+
195
+ if (nodeMajor < 14) {
196
+ error(`Node.js ${nodeVersion} detected. aether-cli requires Node.js >= 14.0.0`);
197
+ error('Please upgrade Node.js and try again.');
198
+ info('Visit: https://nodejs.org/');
199
+ return { ok: false, error: 'Node.js version too old' };
200
+ }
201
+ ok(`Node.js ${nodeVersion} ✓`);
202
+
203
+ // Check npm
204
+ const npmVersion = getNpmVersion();
205
+ info(`npm version: ${npmVersion}`);
206
+ if (npmVersion === 'not found') {
207
+ warn('npm not found in PATH. Will attempt npm global install.');
208
+ } else {
209
+ ok(`npm ${npmVersion} ✓`);
210
+ }
211
+
212
+ // Check disk space
213
+ const homedir = os.homedir();
214
+ try {
215
+ const checkCmd = isWindows
216
+ ? `wmic logicaldisk where "DeviceID='${homedir.substring(0, 2)}'" get FreeSpace /value`
217
+ : `df -k ${homedir} | tail -1 | awk '{print $4}'`;
218
+ const output = execSync(checkCmd, { encoding: 'utf8' }).trim();
219
+ const freeKB = parseInt(output.match(/\d+/)?.[0] || '0', 10);
220
+ const freeMB = freeKB / 1024;
221
+
222
+ if (freeMB < 100) {
223
+ warn(`Only ${Math.round(freeMB)}MB free in ${homedir}`);
224
+ warn('Installing may require more space.');
225
+ } else {
226
+ ok(`${Math.round(freeMB)}MB available ✓`);
227
+ }
228
+ } catch {
229
+ ok('Disk space check skipped ✓');
230
+ }
231
+
232
+ // Check existing installation
233
+ const existing = checkExistingInstall();
234
+ if (existing.installed) {
235
+ warn(`aether-cli is already installed: ${existing.version}`);
236
+ if (!args.force) {
237
+ info('Use --force to reinstall or upgrade');
238
+ return { ok: true, existing: true };
239
+ }
240
+ info('Reinstalling with --force...');
241
+ }
242
+
243
+ return { ok: true };
244
+ }
245
+
246
+ async function stepNpmInstall(args) {
247
+ header('INSTALLING aether-cli');
248
+
249
+ const packageName = args.package || '@jellylegsai/aether-cli';
250
+ const installPath = getInstallPath();
251
+ info(`Package: ${packageName}`);
252
+ info(`Install path: ${installPath}`);
253
+
254
+ step(`Running: npm install -g ${packageName}`);
255
+
256
+ return new Promise((resolve) => {
257
+ const installArgs = ['install', '-g', packageName];
258
+ if (args.registry) {
259
+ installArgs.push('--registry', args.registry);
260
+ }
261
+
262
+ const child = spawn(isWindows ? 'npm.cmd' : 'npm', installArgs, {
263
+ stdio: 'inherit',
264
+ shell: true,
265
+ env: { ...process.env, npm_config_registry: args.registry },
266
+ });
267
+
268
+ child.on('close', (code) => {
269
+ if (code === 0) {
270
+ ok('Package installed successfully ✓');
271
+ resolve({ ok: true });
272
+ } else {
273
+ error(`npm install failed with code ${code}`);
274
+ resolve({ ok: false, error: `npm exit code ${code}` });
275
+ }
276
+ });
277
+
278
+ child.on('error', (err) => {
279
+ error(`Installation failed: ${err.message}`);
280
+ resolve({ ok: false, error: err.message });
281
+ });
282
+ });
283
+ }
284
+
285
+ async function stepConfigInit(args) {
286
+ header('CONFIGURING aether-cli');
287
+
288
+ // Create .aether directory
289
+ const aetherDir = getAetherDir();
290
+ if (!fs.existsSync(aetherDir)) {
291
+ fs.mkdirSync(aetherDir, { recursive: true });
292
+ ok(`Created: ${aetherDir}`);
293
+ } else {
294
+ info(`Config directory exists: ${aetherDir}`);
295
+ }
296
+
297
+ // Check if config.json exists
298
+ const configPath = getConfigPath();
299
+ if (fs.existsSync(configPath)) {
300
+ if (!args.force) {
301
+ info('Config file already exists, skipping init');
302
+ return { ok: true, existing: true };
303
+ }
304
+ info('Overwriting existing config (--force)');
305
+ }
306
+
307
+ // Determine RPC URL
308
+ let rpcUrl = args.rpc || process.env.AETHER_RPC || 'http://127.0.0.1:8899';
309
+
310
+ // Validate RPC URL
311
+ if (!args.skipRpcCheck) {
312
+ step('Validating RPC endpoint...');
313
+ const validation = await validateRpcUrl(rpcUrl);
314
+
315
+ if (validation.valid) {
316
+ ok(`RPC endpoint reachable: ${rpcUrl}`);
317
+ if (validation.latency) {
318
+ const latencyColor = validation.latency < 50 ? C.green : validation.latency < 200 ? C.cyan : C.yellow;
319
+ sub(`Latency: ${latencyColor}${validation.latency}ms${C.reset}`);
320
+ }
321
+ } else {
322
+ warn(`RPC endpoint not reachable: ${rpcUrl}`);
323
+ warn('You can set it later with: aether config set rpc.url <url>');
324
+ sub('Continuing without RPC validation...');
325
+ }
326
+ } else {
327
+ info('Skipping RPC check (--skip-rpc-check)');
328
+ }
329
+
330
+ // Create default config
331
+ const defaultConfig = {
332
+ version: 2,
333
+ created_at: new Date().toISOString(),
334
+ updated_at: new Date().toISOString(),
335
+ rpc: {
336
+ url: rpcUrl,
337
+ backup: args.backupRpc || null,
338
+ timeout: 10000,
339
+ },
340
+ wallet: {
341
+ default: null,
342
+ keypair: null,
343
+ },
344
+ validator: {
345
+ tier: 'full',
346
+ identity: null,
347
+ },
348
+ output: {
349
+ format: 'text',
350
+ colors: true,
351
+ },
352
+ network: {
353
+ explorer: 'https://explorer.aether.network',
354
+ faucet: null,
355
+ },
356
+ };
357
+
358
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
359
+ ok(`Config file created: ${configPath}`);
360
+
361
+ return { ok: true, rpcUrl, configPath };
362
+ }
363
+
364
+ async function stepPathSetup(args) {
365
+ header('PATH CONFIGURATION');
366
+
367
+ const installPath = getInstallPath();
368
+ const cliBin = path.join(installPath, 'aether-cli', 'index.js');
369
+
370
+ // Check if CLI is already accessible
371
+ const existingPath = which('aether') || which('aether-cli');
372
+ if (existingPath) {
373
+ ok(`aether-cli is already in PATH: ${existingPath}`);
374
+ return { ok: true, pathSetup: 'existing' };
375
+ }
376
+
377
+ // Determine what PATH changes are needed
378
+ const pathsToAdd = [];
379
+
380
+ if (isWindows) {
381
+ // Add npm global to PATH if not already there
382
+ const npmGlobalPath = installPath.replace(/\\lib\\node_modules$/, '');
383
+ const npmBinPath = path.join(npmGlobalPath, 'bin');
384
+ pathsToAdd.push(npmBinPath);
385
+ } else {
386
+ // Unix - add npm global bin
387
+ pathsToAdd.push(path.join(installPath, '..', 'bin'));
388
+ }
389
+
390
+ // Check if paths are already in PATH
391
+ const currentPath = process.env.PATH || '';
392
+ const newPaths = pathsToAdd.filter(p => !currentPath.includes(p));
393
+
394
+ if (newPaths.length === 0) {
395
+ ok('CLI is accessible in current PATH');
396
+ return { ok: true, pathSetup: 'ok' };
397
+ }
398
+
399
+ // Provide instructions
400
+ if (isWindows) {
401
+ warn('aether-cli is installed but may not be in PATH');
402
+ info('Add to PATH: Settings → System → Environment Variables → Path');
403
+ info(`Add this directory:`);
404
+ sub(newPaths[0]);
405
+ } else if (isMac || isLinux) {
406
+ warn('aether-cli is installed but may not be in PATH');
407
+ info('Add to PATH by adding this to your ~/.bashrc or ~/.zshrc:');
408
+ sub(`export PATH="${newPaths[0]}:$PATH"`);
409
+ }
410
+
411
+ return { ok: true, pathSetup: 'needs_config', paths: newPaths };
412
+ }
413
+
414
+ async function stepPostInstall(args) {
415
+ header('POST-INSTALLATION VERIFICATION');
416
+
417
+ // Find the CLI
418
+ const cliPath = which('aether') || which('aether-cli');
419
+
420
+ if (cliPath) {
421
+ ok(`aether-cli found at: ${cliPath}`);
422
+
423
+ // Try to get version
424
+ try {
425
+ const version = execSync(`"${cliPath}" --version`, { encoding: 'utf8', shell: true }).trim();
426
+ sub(`Version: ${version}`);
427
+ } catch {
428
+ sub('Version check skipped');
429
+ }
430
+
431
+ // Run doctor to verify
432
+ try {
433
+ step('Running health check...');
434
+ execSync(`"${cliPath}" doctor --tier lite`, { stdio: 'inherit', shell: true });
435
+ } catch {
436
+ warn('Health check had issues (this may be normal if no RPC is running)');
437
+ }
438
+ } else {
439
+ warn('aether-cli not found in PATH after installation');
440
+ info('Try opening a new terminal or restart your shell');
441
+ info('Then run: aether-cli --version');
442
+ }
443
+
444
+ return { ok: true };
445
+ }
446
+
447
+ function showUninstall() {
448
+ return new Promise((resolve) => {
449
+ header('UNINSTALLING aether-cli');
450
+
451
+ step('Removing npm global package...');
452
+
453
+ try {
454
+ execSync(isWindows ? 'npm.cmd uninstall -g @jellylegsai/aether-cli' : 'npm uninstall -g @jellylegsai/aether-cli', {
455
+ stdio: 'inherit',
456
+ shell: true,
457
+ });
458
+ ok('Package removed ✓');
459
+ } catch (err) {
460
+ warn('Failed to remove package: ' + err.message);
461
+ }
462
+
463
+ // Ask about config
464
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
465
+ rl.question(`\n ${C.yellow}Remove config directory ~/.aether? [y/N]${C.reset} `, (answer) => {
466
+ rl.close();
467
+
468
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
469
+ const aetherDir = getAetherDir();
470
+ if (fs.existsSync(aetherDir)) {
471
+ try {
472
+ fs.rmSync(aetherDir, { recursive: true, force: true });
473
+ ok(`Removed: ${aetherDir}`);
474
+ } catch (err) {
475
+ warn(`Failed to remove ${aetherDir}: ${err.message}`);
476
+ }
477
+ }
478
+ } else {
479
+ info('Config directory preserved');
480
+ }
481
+
482
+ log('\n aether-cli has been uninstalled', C.green);
483
+ log(' You may need to restart your terminal', C.dim);
484
+ log(' Thanks for trying aether-cli!\n', C.dim);
485
+
486
+ resolve();
487
+ });
488
+ });
489
+ }
490
+
491
+ function showUsage() {
492
+ log(`
493
+ ${C.bright}${C.cyan}aether-cli install${C.reset} — Install or upgrade aether-cli
494
+
495
+ ${C.bright}SYNOPSIS${C.reset}
496
+ aether-cli install [--force] [--rpc <url>] [--skip-rpc-check]
497
+ aether-cli install --uninstall
498
+
499
+ ${C.bright}DESCRIPTION${C.reset}
500
+ Installs or upgrades @jellylegsai/aether-cli globally via npm.
501
+ Creates ~/.aether/config.json with your RPC endpoint.
502
+ Checks Node.js version and disk space.
503
+ Configures PATH for CLI access.
504
+
505
+ ${C.bright}OPTIONS${C.reset}
506
+ --force Overwrite existing config
507
+ --rpc <url> RPC endpoint URL (default: http://127.0.0.1:8899)
508
+ --backup-rpc <url> Backup RPC endpoint
509
+ --skip-rpc-check Skip RPC validation
510
+ --registry <url> npm registry URL
511
+ --package <name> Package to install (default: @jellylegsai/aether-cli)
512
+ --uninstall Remove aether-cli and optional config
513
+ --help, -h Show this help
514
+
515
+ ${C.bright}EXAMPLES${C.reset}
516
+ aether-cli install
517
+ aether-cli install --rpc https://mainnet.aether.network:8899
518
+ aether-cli install --skip-rpc-check --force
519
+ aether-cli install --uninstall
520
+
521
+ ${C.bright}PLATFORM NOTES${C.reset}
522
+ ${C.cyan}Windows:${C.reset} Adds npm global bin to PATH via Environment Variables
523
+ ${C.cyan}macOS:${C.reset} Add to ~/.zshrc: export PATH="\$(npm root -g)/aether-cli:$PATH"
524
+ ${C.cyan}Linux:${C.reset} Same as macOS
525
+
526
+ ${C.bright}SYSTEM REQUIREMENTS${C.reset}
527
+ • Node.js >= 14.0.0
528
+ • npm >= 6.0.0
529
+ • 100MB free disk space
530
+ • Network access for npm and RPC endpoints
531
+
532
+ ${C.bright}QUICK INSTALL (via npm)${C.reset}
533
+ npm install -g @jellylegsai/aether-cli
534
+ aether-cli --version
535
+
536
+ ${C.bright}QUICK INSTALL (via script)${C.reset}
537
+ curl -sSL https://get.aether.network/install.sh | bash
538
+ `, C.reset);
539
+ }
540
+
541
+ // ============================================================================
542
+ // Main Installer
543
+ // ============================================================================
544
+
545
+ async function main() {
546
+ const args = parseArgs();
547
+
548
+ // Check for uninstall
549
+ if (args.uninstall) {
550
+ await showUninstall();
551
+ return;
552
+ }
553
+
554
+ // Check for help
555
+ if (args.help) {
556
+ showUsage();
557
+ return;
558
+ }
559
+
560
+ console.log(`
561
+ ${C.bright}${C.cyan}
562
+ ╔═══════════════════════════════════════════════════════════╗
563
+ ║ aether-cli Installer v${VERSION.toString().padEnd(32)}║
564
+ ║ Platform: ${platform.toUpperCase().padEnd(40)}║
565
+ ╚═══════════════════════════════════════════════════════════╝${C.reset}
566
+ `);
567
+
568
+ log(` ${C.dim}Node.js ${getNodeVersion()} | npm ${getNpmVersion()}${C.reset}\n`);
569
+
570
+ // Run installation steps
571
+ const preCheck = await stepPreInstall(args);
572
+ if (!preCheck.ok) {
573
+ error('Pre-installation checks failed');
574
+ process.exit(1);
575
+ }
576
+
577
+ separator();
578
+
579
+ const npmResult = await stepNpmInstall(args);
580
+ if (!npmResult.ok) {
581
+ error('npm installation failed');
582
+ process.exit(1);
583
+ }
584
+
585
+ separator();
586
+
587
+ const configResult = await stepConfigInit(args);
588
+
589
+ separator();
590
+
591
+ const pathResult = await stepPathSetup(args);
592
+
593
+ separator();
594
+
595
+ const postResult = await stepPostInstall(args);
596
+
597
+ separator();
598
+
599
+ header('INSTALLATION COMPLETE');
600
+ ok('aether-cli is ready to use!');
601
+
602
+ log('\n Next steps:', C.bright);
603
+ log(' aether-cli --version Verify installation', C.dim);
604
+ log(' aether-cli doctor Run system checks', C.dim);
605
+ log(' aether-cli init Start onboarding', C.dim);
606
+ log(' aether-cli --help Show all commands', C.dim);
607
+
608
+ if (pathResult.pathSetup === 'needs_config') {
609
+ log('\n ⚠ PATH update required:', C.yellow);
610
+ log(` Add this to your shell profile:`, C.dim);
611
+ log(` export PATH="${pathResult.paths[0]}:$PATH"`, C.cyan);
612
+ }
613
+
614
+ log('\n');
615
+ }
616
+
617
+ function parseArgs() {
618
+ const rawArgs = process.argv.slice(2);
619
+ const args = {
620
+ force: rawArgs.includes('--force') || rawArgs.includes('-f'),
621
+ uninstall: rawArgs.includes('--uninstall'),
622
+ help: rawArgs.includes('--help') || rawArgs.includes('-h'),
623
+ skipRpcCheck: rawArgs.includes('--skip-rpc-check'),
624
+ rpc: null,
625
+ backupRpc: null,
626
+ registry: null,
627
+ package: null,
628
+ };
629
+
630
+ const rpcIdx = rawArgs.findIndex(a => a === '--rpc' || a === '-r');
631
+ if (rpcIdx !== -1 && rawArgs[rpcIdx + 1] && !rawArgs[rpcIdx + 1].startsWith('--')) {
632
+ args.rpc = rawArgs[rpcIdx + 1];
633
+ }
634
+
635
+ const backupIdx = rawArgs.findIndex(a => a === '--backup-rpc');
636
+ if (backupIdx !== -1 && rawArgs[backupIdx + 1] && !rawArgs[backupIdx + 1].startsWith('--')) {
637
+ args.backupRpc = rawArgs[backupIdx + 1];
638
+ }
639
+
640
+ const regIdx = rawArgs.findIndex(a => a === '--registry');
641
+ if (regIdx !== -1 && rawArgs[regIdx + 1] && !rawArgs[regIdx + 1].startsWith('--')) {
642
+ args.registry = rawArgs[regIdx + 1];
643
+ }
644
+
645
+ const pkgIdx = rawArgs.findIndex(a => a === '--package');
646
+ if (pkgIdx !== -1 && rawArgs[pkgIdx + 1] && !rawArgs[pkgIdx + 1].startsWith('--')) {
647
+ args.package = rawArgs[pkgIdx + 1];
648
+ }
649
+
650
+ return args;
651
+ }
652
+
653
+ // Export as installCommand for CLI use
654
+ function installCommand() {
655
+ main().catch(err => {
656
+ console.error(`\n${C.red}✗ Installation failed:${C.reset}`, err.message, '\n');
657
+ process.exit(1);
658
+ });
659
+ }
660
+
661
+ // Run if called directly
662
+ if (require.main === module) {
663
+ installCommand();
664
+ }
665
+
666
+ module.exports = { installCommand };