@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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/aether-cli-1.0.0.tgz +0 -0
- package/aether-cli-1.8.0.tgz +0 -0
- package/aether-hub-1.0.5.tgz +0 -0
- package/aether-hub-1.1.8.tgz +0 -0
- package/aether-hub-1.2.1.tgz +0 -0
- package/commands/account.js +280 -0
- package/commands/apy.js +499 -0
- package/commands/balance.js +241 -0
- package/commands/blockhash.js +181 -0
- package/commands/broadcast.js +387 -0
- package/commands/claim.js +490 -0
- package/commands/config.js +851 -0
- package/commands/delegations.js +582 -0
- package/commands/doctor.js +769 -0
- package/commands/emergency.js +667 -0
- package/commands/epoch.js +275 -0
- package/commands/fees.js +276 -0
- package/commands/index.js +78 -0
- package/commands/info.js +495 -0
- package/commands/init.js +816 -0
- package/commands/install.js +666 -0
- package/commands/kyc.js +272 -0
- package/commands/logs.js +315 -0
- package/commands/monitor.js +431 -0
- package/commands/multisig.js +701 -0
- package/commands/network.js +429 -0
- package/commands/nft.js +857 -0
- package/commands/ping.js +266 -0
- package/commands/price.js +253 -0
- package/commands/rewards.js +931 -0
- package/commands/sdk-test.js +477 -0
- package/commands/sdk.js +656 -0
- package/commands/slot.js +155 -0
- package/commands/snapshot.js +470 -0
- package/commands/stake-info.js +139 -0
- package/commands/stake-positions.js +205 -0
- package/commands/stake.js +516 -0
- package/commands/stats.js +396 -0
- package/commands/status.js +327 -0
- package/commands/supply.js +391 -0
- package/commands/tps.js +238 -0
- package/commands/transfer.js +495 -0
- package/commands/tx-history.js +346 -0
- package/commands/unstake.js +597 -0
- package/commands/validator-info.js +657 -0
- package/commands/validator-register.js +593 -0
- package/commands/validator-start.js +323 -0
- package/commands/validator-status.js +227 -0
- package/commands/validators.js +626 -0
- package/commands/wallet.js +1570 -0
- package/index.js +593 -0
- package/lib/errors.js +398 -0
- package/package.json +76 -0
- package/sdk/README.md +210 -0
- package/sdk/index.js +1639 -0
- package/sdk/package.json +34 -0
- package/sdk/rpc.js +254 -0
- package/sdk/test.js +85 -0
- package/test/doctor.test.js +76 -0
- 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 };
|