Merge branch 'master' into feature/upload-download

# Conflicts:
#	src/handlers/installationHandlers.js
#	src/handlers/nodeHandlers.js
This commit is contained in:
Ben 2025-07-02 09:20:22 +02:00
commit b3c7a62a3f
No known key found for this signature in database
GPG Key ID: 0F16E812E736C24B
2 changed files with 418 additions and 351 deletions

View File

@ -115,6 +115,46 @@ async function clearCodexExePathFromConfig(config) {
saveConfig(config); saveConfig(config);
} }
async function configureShellPath(installPath) {
try {
const homedir = os.homedir();
let shellConfigFile;
let exportLine = `export PATH="${installPath}:$PATH"`;
if (platform === 'win32') {
// For Windows, update the User PATH environment variable
const currentPath = process.env.PATH || '';
if (!currentPath.includes(installPath)) {
await runCommand(`setx PATH "${installPath};%PATH%"`);
}
return true;
} else {
// For Unix-like systems (macOS and Linux)
if (platform === 'darwin') {
// Check for zsh first (default on modern macOS)
if (fs.existsSync(path.join(homedir, '.zshrc'))) {
shellConfigFile = path.join(homedir, '.zshrc');
} else {
shellConfigFile = path.join(homedir, '.bash_profile');
}
} else {
// Linux
shellConfigFile = path.join(homedir, '.bashrc');
}
// Check if PATH is already configured
const fileContent = fs.existsSync(shellConfigFile) ? fs.readFileSync(shellConfigFile, 'utf8') : '';
if (!fileContent.includes(installPath)) {
fs.appendFileSync(shellConfigFile, `\n${exportLine}\n`);
}
return true;
}
} catch (error) {
console.log(showErrorMessage(`Failed to configure PATH: ${error.message}`));
return false;
}
}
async function performInstall(config) { async function performInstall(config) {
const agreed = await showPrivacyDisclaimer(); const agreed = await showPrivacyDisclaimer();
if (!agreed) { if (!agreed) {
@ -185,34 +225,114 @@ async function performInstall(config) {
}; };
die if $@; die if $@;
'`; '`;
await runCommand(timeoutCommand); await runCommand(timeoutCommand);
} else { } else {
await runCommand( await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`);
`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`, }
);
await saveCodexExePath(config, path.join(installPath, "codex"));
} catch (error) {
if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
throw new Error('Installation failed. Please check your internet connection and try again.');
} else if (error.message.includes('Permission denied')) {
throw new Error('Permission denied. Please try running the command with sudo.');
} else if (error.message.includes('timeout')) {
throw new Error('Installation is taking longer than expected. Please try running with sudo.');
} else {
throw new Error('Installation failed. Please try running with sudo if you haven\'t already.');
}
} finally {
await runCommand('rm -f install.sh').catch(() => {});
}
}
try {
const version = await getCodexVersion(config);
console.log(chalk.green(version));
// Configure shell PATH
const pathConfigured = await configureShellPath(installPath);
spinner.success();
if (pathConfigured) {
console.log(boxen(
chalk.green('Codex is installed successfully!\n\n') +
chalk.cyan('Current configuration:\n') +
`${chalk.white('Data path')} = ${config.dataDir}\n` +
`${chalk.white('Logs path')} = ${config.logsDir}\n` +
`${chalk.white('Storage quota')} = ${config.storageQuota} Bytes\n` +
`${chalk.white('Discovery port')} = ${config.ports.discPort}\n` +
`${chalk.white('P2P Listen port')} = ${config.ports.listenPort}\n` +
`${chalk.white('API Port')} = ${config.ports.apiPort}\n\n` +
chalk.gray('You can modify the above configuration from the main menu.'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
title: '✅ Installation Complete',
titleAlignment: 'center'
}
));
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'What would you like to do?',
choices: [
'1. Return to main menu',
'2. Exit',
],
pageSize: 2,
loop: true
}
]);
switch (choice.split('.')[0].trim()) {
case '1':
await showNavigationMenu();
break;
case '2':
process.exit(0);
}
} else {
console.log(showInfoMessage(
'Codex is installed but PATH configuration failed.\n' +
`Please manually add "${installPath}" to your PATH.\n`
));
}
} catch (error) {
throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.');
} }
await saveCodexExePath(config, path.join(installPath, "codex")); console.log(showInfoMessage(
} catch (error) { "Please review the configuration before starting Codex."
if ( ));
error.message.includes("ECONNREFUSED") ||
error.message.includes("ETIMEDOUT") return true;
) { } catch (error) {
throw new Error( spinner.error();
"Installation failed. Please check your internet connection and try again.", console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
); return false;
} else if (error.message.includes("Permission denied")) { }
throw new Error( }
"Permission denied. Please try running the command with sudo.",
); function removeDir(dir) {
} else if (error.message.includes("timeout")) { fs.rmSync(dir, { recursive: true, force: true });
throw new Error( }
"Installation is taking longer than expected. Please try running with sudo.",
); export async function uninstallCodex(config, showNavigationMenu) {
} else { const { confirm } = await inquirer.prompt([
throw new Error( {
"Installation failed. Please try running with sudo if you haven't already.", type: 'confirm',
); name: 'confirm',
message: chalk.yellow(
'⚠️ Are you sure you want to uninstall Codex? This action cannot be undone. \n' +
'All data stored in the local Codex node will be deleted as well.'
),
default: false
} }
} finally { } finally {
await runCommand("rm -f install.sh").catch(() => {}); await runCommand("rm -f install.sh").catch(() => {});

View File

@ -1,349 +1,296 @@
import path from "path"; import path from 'path';
import { createSpinner } from "nanospinner"; import { createSpinner } from 'nanospinner';
import { runCommand } from "../utils/command.js"; import { runCommand } from '../utils/command.js';
import { import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
showErrorMessage, import { isNodeRunning, isCodexInstalled, startPeriodicLogging, getWalletAddress, setWalletAddress } from '../services/nodeService.js';
showInfoMessage, import inquirer from 'inquirer';
showSuccessMessage, import boxen from 'boxen';
} from "../utils/messages.js"; import chalk from 'chalk';
import { import os from 'os';
isNodeRunning, import { exec } from 'child_process';
isCodexInstalled, import axios from 'axios';
startPeriodicLogging,
getWalletAddress,
setWalletAddress,
} from "../services/nodeService.js";
import inquirer from "inquirer";
import boxen from "boxen";
import chalk from "chalk";
import os from "os";
import { exec } from "child_process";
import axios from "axios";
const platform = os.platform(); const platform = os.platform();
async function promptForWalletAddress() { async function promptForWalletAddress() {
const { wallet } = await inquirer.prompt([ const { wallet } = await inquirer.prompt([
{ {
type: "input", type: 'input',
name: "wallet", name: 'wallet',
message: message: 'Please enter your ERC20 wallet address (or press enter to skip):',
"Please enter your ERC20 wallet address (or press enter to skip):", validate: (input) => {
validate: (input) => { if (!input) return true; // Allow empty input
if (!input) return true; // Allow empty input if (/^0x[a-fA-F0-9]{40}$/.test(input)) return true;
if (/^0x[a-fA-F0-9]{40}$/.test(input)) return true; return 'Please enter a valid ERC20 wallet address (0x followed by 40 hexadecimal characters) or press enter to skip';
return "Please enter a valid ERC20 wallet address (0x followed by 40 hexadecimal characters) or press enter to skip"; }
}, }
}, ]);
]); return wallet || null;
return wallet || null;
} }
function getCurrentLogFile(config) { function getCurrentLogFile(config) {
const timestamp = new Date() const timestamp = new Date().toISOString()
.toISOString() .replaceAll(":", "-")
.replaceAll(":", "-") .replaceAll(".", "-");
.replaceAll(".", "-"); return path.join(config.logsDir, `codex_${timestamp}.log`);
return path.join(config.logsDir, `codex_${timestamp}.log`);
} }
export async function runCodex(config, showNavigationMenu) { export async function runCodex(config, showNavigationMenu) {
const isInstalled = await isCodexInstalled(config); const isInstalled = await isCodexInstalled(config);
if (!isInstalled) { if (!isInstalled) {
console.log( console.log(showErrorMessage('Codex is not installed. Please install Codex first using option 1 from the main menu.'));
showErrorMessage( await showNavigationMenu();
"Codex is not installed. Please install Codex first using option 1 from the main menu.", return;
), }
);
await showNavigationMenu(); const nodeAlreadyRunning = await isNodeRunning(config);
return;
} if (nodeAlreadyRunning) {
console.log(showInfoMessage('A Codex node is already running.'));
const nodeAlreadyRunning = await isNodeRunning(config); await showNavigationMenu();
} else {
if (nodeAlreadyRunning) { try {
console.log(showInfoMessage("A Codex node is already running.")); let nat;
await showNavigationMenu(); if (platform === 'win32') {
} else { const result = await runCommand('for /f "delims=" %a in (\'curl -s --ssl-reqd ip.codex.storage\') do @echo %a');
try { nat = result.trim();
let nat; } else {
if (platform === "win32") { nat = await runCommand('curl -s https://ip.codex.storage');
const result = await runCommand( }
"for /f \"delims=\" %a in ('curl -s --ssl-reqd ip.codex.storage') do @echo %a",
); if (config.dataDir.length < 1) throw new Error("Missing config: dataDir");
nat = result.trim(); if (config.logsDir.length < 1) throw new Error("Missing config: logsDir");
} else { const logFilePath = getCurrentLogFile(config);
nat = await runCommand("curl -s https://ip.codex.storage");
} console.log(showInfoMessage(
`Data location: ${config.dataDir}\n` +
if (config.dataDir.length < 1) throw new Error("Missing config: dataDir"); `Logs: ${logFilePath}\n` +
if (config.logsDir.length < 1) throw new Error("Missing config: logsDir"); `API port: ${config.ports.apiPort}`
const logFilePath = getCurrentLogFile(config); ));
console.log( const executable = config.codexExe;
showInfoMessage( const args = [
`Data location: ${config.dataDir}\n` + `--data-dir="${config.dataDir}"`,
`Logs: ${logFilePath}\n` + `--api-cors-origin="*"`,
`API port: ${config.ports.apiPort}`, `--storage-quota=11811160064`,
), `--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`,
); `--bootstrap-node=spr:CiUIAhIhAyUvcPkKoGE7-gh84RmKIPHJPdsX5Ugm_IHVJgF-Mmu_EgIDARo8CicAJQgCEiEDJS9w-QqgYTv6CHzhGYog8ck92xflSCb8gdUmAX4ya78QoemesAYaCwoJBES39Q2RAnVOKkYwRAIgLi3rouyaZFS_Uilx8k99ySdQCP1tsmLR21tDb9p8LcgCIG30o5YnEooQ1n6tgm9fCT7s53k6XlxyeSkD_uIO9mb3`,
`--bootstrap-node=spr:CiUIAhIhA7E4DEMer8nUOIUSaNPA4z6x0n9Xaknd28Cfw9S2-cCeEgIDARo8CicAJQgCEiEDsTgMQx6vydQ4hRJo08DjPrHSf1dqSd3bwJ_D1Lb5wJ4Qt_CesAYaCwoJBEDhWZORAnVYKkYwRAIgFNzhnftocLlVHJl1onuhbSUM7MysXPV6dawHAA0DZNsCIDRVu9gnPTH5UkcRXLtt7MLHCo4-DL-RCMyTcMxYBXL0`,
const executable = config.codexExe; `--bootstrap-node=spr:CiUIAhIhAzZn3JmJab46BNjadVnLNQKbhnN3eYxwqpteKYY32SbOEgIDARo8CicAJQgCEiEDNmfcmYlpvjoE2Np1Wcs1ApuGc3d5jHCqm14phjfZJs4QrvWesAYaCwoJBKpA-TaRAnViKkcwRQIhANuMmZDD2c25xzTbKSirEpkZYoxbq-FU_lpI0K0e4mIVAiBfQX4yR47h1LCnHznXgDs6xx5DLO5q3lUcicqUeaqGeg`,
const args = [ `--bootstrap-node=spr:CiUIAhIhAgybmRwboqDdUJjeZrzh43sn5mp8jt6ENIb08tLn4x01EgIDARo8CicAJQgCEiECDJuZHBuioN1QmN5mvOHjeyfmanyO3oQ0hvTy0ufjHTUQh4ifsAYaCwoJBI_0zSiRAnVsKkcwRQIhAJCb_z0E3RsnQrEePdJzMSQrmn_ooHv6mbw1DOh5IbVNAiBbBJrWR8eBV6ftzMd6ofa5khNA2h88OBhMqHCIzSjCeA`,
`--data-dir="${config.dataDir}"`, `--bootstrap-node=spr:CiUIAhIhAntGLadpfuBCD9XXfiN_43-V3L5VWgFCXxg4a8uhDdnYEgIDARo8CicAJQgCEiECe0Ytp2l-4EIP1dd-I3_jf5XcvlVaAUJfGDhry6EN2dgQsIufsAYaCwoJBNEmoCiRAnV2KkYwRAIgXO3bzd5VF8jLZG8r7dcLJ_FnQBYp1BcxrOvovEa40acCIDhQ14eJRoPwJ6GKgqOkXdaFAsoszl-HIRzYcXKeb7D9`,
`--log-level=DEBUG`, `--log-level=DEBUG`,
`--log-file="${logFilePath}"`, `--log-file="${logFilePath}"`,
`--storage-quota="${config.storageQuota}"`, `--storage-quota="${config.storageQuota}"`,
`--disc-port=${config.ports.discPort}`, `--disc-port=${config.ports.discPort}`,
`--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`, `--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`,
`--api-port=${config.ports.apiPort}`, `--api-port=${config.ports.apiPort}`,
`--nat=${nat}`, `--nat=extip:${nat}`,
`--api-cors-origin="*"`, `--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`
`--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`, ];
];
const command =
const command = `"${executable}" ${args.join(" ")}`; `"${executable}" ${args.join(" ")}`
console.log( console.log(showInfoMessage(
showInfoMessage( '🚀 Codex node is running...\n\n' +
"🚀 Codex node is running...\n\n" + 'If your firewall ask, be sure to allow Codex to receive connections. \n' +
"If your firewall ask, be sure to allow Codex to receive connections. \n" + 'Please keep this terminal open. Start a new terminal to interact with the node.\n\n' +
"Please keep this terminal open. Start a new terminal to interact with the node.\n\n" + 'Press CTRL+C to stop the node'
"Press CTRL+C to stop the node", ));
),
); const nodeProcess = exec(command);
const nodeProcess = exec(command); await new Promise(resolve => setTimeout(resolve, 5000));
await new Promise((resolve) => setTimeout(resolve, 5000)); try {
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
try { if (response.status === 200) {
const response = await axios.get( // Check if wallet exists
`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`, try {
); const existingWallet = await getWalletAddress();
if (response.status === 200) { if (!existingWallet) {
// Check if wallet exists console.log(showInfoMessage('[OPTIONAL] Please provide your ERC20 wallet address.'));
try { const wallet = await promptForWalletAddress();
const existingWallet = await getWalletAddress(); if (wallet) {
if (!existingWallet) { await setWalletAddress(wallet);
console.log( console.log(showSuccessMessage('Wallet address saved successfully!'));
showInfoMessage( }
"[OPTIONAL] Please provide your ERC20 wallet address.", }
), } catch (error) {
); console.log(showErrorMessage('Failed to process wallet address. Continuing without wallet update.'));
const wallet = await promptForWalletAddress(); }
if (wallet) {
await setWalletAddress(wallet); // Start periodic logging
console.log( const stopLogging = await startPeriodicLogging(config);
showSuccessMessage("Wallet address saved successfully!"),
); nodeProcess.on('exit', () => {
} stopLogging();
} });
} catch (error) {
console.log( console.log(boxen(
showErrorMessage( chalk.cyan('We are logging some of your node\'s public data for improving the Codex experience'),
"Failed to process wallet address. Continuing without wallet update.", {
), padding: 1,
); margin: 1,
} borderStyle: 'round',
borderColor: 'cyan',
// Start periodic logging title: '🔒 Privacy Notice',
const stopLogging = await startPeriodicLogging(config); titleAlignment: 'center',
dimBorder: true
nodeProcess.on("exit", () => { }
stopLogging(); ));
}); }
} catch (error) {
console.log( // Silently handle any logging errors
boxen( }
chalk.cyan(
"We are logging some of your node's public data for improving the Codex experience", await new Promise((resolve, reject) => {
), nodeProcess.on('exit', (code) => {
{ if (code === 0) resolve();
padding: 1, else reject(new Error(`Node exited with code ${code}`));
margin: 1, });
borderStyle: "round", });
borderColor: "cyan",
title: "🔒 Privacy Notice", if (platform === 'win32') {
titleAlignment: "center", console.log(showInfoMessage('Cleaning up firewall rules...'));
dimBorder: true, await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (TCP-In)"');
}, await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (UDP-In)"');
), }
);
} } catch (error) {
} catch (error) { console.log(showErrorMessage(`Failed to run Codex: ${error.message}`));
// Silently handle any logging errors }
} await showNavigationMenu();
await new Promise((resolve, reject) => {
nodeProcess.on("exit", (code) => {
if (code === 0) resolve();
else reject(new Error(`Node exited with code ${code}`));
});
});
if (platform === "win32") {
console.log(showInfoMessage("Cleaning up firewall rules..."));
await runCommand(
'netsh advfirewall firewall delete rule name="Allow Codex (TCP-In)"',
);
await runCommand(
'netsh advfirewall firewall delete rule name="Allow Codex (UDP-In)"',
);
}
} catch (error) {
console.log(showErrorMessage(`Failed to run Codex: ${error.message}`));
} }
await showNavigationMenu();
}
} }
async function showNodeDetails(data, showNavigationMenu) { async function showNodeDetails(data, showNavigationMenu) {
const { choice } = await inquirer.prompt([ const { choice } = await inquirer.prompt([
{ {
type: "list", type: 'list',
name: "choice", name: 'choice',
message: "Select information to view:", message: 'Select information to view:',
choices: [ choices: [
"1. View Connected Peers", '1. View Connected Peers',
"2. View Node Information", '2. View Node Information',
"3. Update Wallet Address", '3. Update Wallet Address',
"4. Back to Main Menu", '4. Back to Main Menu',
"5. Exit", '5. Exit'
], ],
pageSize: 5, pageSize: 5,
loop: true, loop: true
},
]);
switch (choice.split(".")[0].trim()) {
case "1":
const peerCount = data.table.nodes.length;
if (peerCount > 0) {
console.log(showInfoMessage("Connected Peers"));
data.table.nodes.forEach((node, index) => {
console.log(
boxen(
`Peer ${index + 1}:\n` +
`${chalk.cyan("Peer ID:")} ${node.peerId}\n` +
`${chalk.cyan("Address:")} ${node.address}\n` +
`${chalk.cyan("Status:")} ${node.seen ? chalk.green("Active") : chalk.gray("Inactive")}`,
{
padding: 1,
margin: 1,
borderStyle: "round",
borderColor: "blue",
},
),
);
});
} else {
console.log(showInfoMessage("No connected peers found."));
}
return showNodeDetails(data, showNavigationMenu);
case "2":
console.log(
boxen(
`${chalk.cyan("Version:")} ${data.codex.version}\n` +
`${chalk.cyan("Revision:")} ${data.codex.revision}\n\n` +
`${chalk.cyan("Node ID:")} ${data.table.localNode.nodeId}\n` +
`${chalk.cyan("Peer ID:")} ${data.table.localNode.peerId}\n` +
`${chalk.cyan("Listening Address:")} ${data.table.localNode.address}\n\n` +
`${chalk.cyan("Public IP:")} ${data.announceAddresses[0].split("/")[2]}\n` +
`${chalk.cyan("Port:")} ${data.announceAddresses[0].split("/")[4]}\n` +
`${chalk.cyan("Connected Peers:")} ${data.table.nodes.length}`,
{
padding: 1,
margin: 1,
borderStyle: "round",
borderColor: "yellow",
title: "📊 Node Information",
titleAlignment: "center",
},
),
);
return showNodeDetails(data, showNavigationMenu);
case "3":
try {
const existingWallet = await getWalletAddress();
console.log(
boxen(
`${chalk.cyan("Current wallet address:")}\n${existingWallet || "Not set"}`,
{
padding: 1,
margin: 1,
borderStyle: "round",
borderColor: "blue",
},
),
);
const wallet = await promptForWalletAddress();
if (wallet) {
await setWalletAddress(wallet);
console.log(
showSuccessMessage("Wallet address updated successfully!"),
);
} }
} catch (error) { ]);
console.log(
showErrorMessage(`Failed to update wallet address: ${error.message}`), switch (choice.split('.')[0].trim()) {
); case '1':
} const peerCount = data.table.nodes.length;
return showNodeDetails(data, showNavigationMenu); if (peerCount > 0) {
case "4": console.log(showInfoMessage('Connected Peers'));
return showNavigationMenu(); data.table.nodes.forEach((node, index) => {
case "5": console.log(boxen(
process.exit(0); `Peer ${index + 1}:\n` +
} `${chalk.cyan('Peer ID:')} ${node.peerId}\n` +
`${chalk.cyan('Address:')} ${node.address}\n` +
`${chalk.cyan('Status:')} ${node.seen ? chalk.green('Active') : chalk.gray('Inactive')}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue'
}
));
});
} else {
console.log(showInfoMessage('No connected peers found.'));
}
return showNodeDetails(data, showNavigationMenu);
case '2':
console.log(boxen(
`${chalk.cyan('Version:')} ${data.codex.version}\n` +
`${chalk.cyan('Revision:')} ${data.codex.revision}\n\n` +
`${chalk.cyan('Node ID:')} ${data.table.localNode.nodeId}\n` +
`${chalk.cyan('Peer ID:')} ${data.table.localNode.peerId}\n` +
`${chalk.cyan('Listening Address:')} ${data.table.localNode.address}\n\n` +
`${chalk.cyan('Public IP:')} ${data.announceAddresses[0].split('/')[2]}\n` +
`${chalk.cyan('Port:')} ${data.announceAddresses[0].split('/')[4]}\n` +
`${chalk.cyan('Connected Peers:')} ${data.table.nodes.length}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
title: '📊 Node Information',
titleAlignment: 'center'
}
));
return showNodeDetails(data, showNavigationMenu);
case '3':
try {
const existingWallet = await getWalletAddress();
console.log(boxen(
`${chalk.cyan('Current wallet address:')}\n${existingWallet || 'Not set'}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue'
}
));
const wallet = await promptForWalletAddress();
if (wallet) {
await setWalletAddress(wallet);
console.log(showSuccessMessage('Wallet address updated successfully!'));
}
} catch (error) {
console.log(showErrorMessage(`Failed to update wallet address: ${error.message}`));
}
return showNodeDetails(data, showNavigationMenu);
case '4':
return showNavigationMenu();
case '5':
process.exit(0);
}
} }
export async function checkNodeStatus(config, showNavigationMenu) { export async function checkNodeStatus(config, showNavigationMenu) {
try { try {
const nodeRunning = await isNodeRunning(config); const nodeRunning = await isNodeRunning(config);
if (nodeRunning) { if (nodeRunning) {
const spinner = createSpinner("Checking node status...").start(); const spinner = createSpinner('Checking node status...').start();
const response = await runCommand( const response = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`, spinner.success();
);
spinner.success();
const data = JSON.parse(response); const data = JSON.parse(response);
const peerCount = data.table.nodes.length; const peerCount = data.table.nodes.length;
const isOnline = peerCount > 2; const isOnline = peerCount > 2;
console.log( console.log(boxen(
boxen( isOnline
isOnline ? chalk.green('Node is ONLINE & DISCOVERABLE')
? chalk.green("Node is ONLINE & DISCOVERABLE") : chalk.yellow('Node is ONLINE but has few peers'),
: chalk.yellow("Node is ONLINE but has few peers"), {
{ padding: 1,
padding: 1, margin: 1,
margin: 1, borderStyle: 'round',
borderStyle: "round", borderColor: isOnline ? 'green' : 'yellow',
borderColor: isOnline ? "green" : "yellow", title: '🔌 Node Status',
title: "🔌 Node Status", titleAlignment: 'center'
titleAlignment: "center", }
}, ));
),
);
await showNodeDetails(data, showNavigationMenu); await showNodeDetails(data, showNavigationMenu);
} else { } else {
console.log( console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
showErrorMessage( await showNavigationMenu();
"Codex node is not running. Try again after starting the node", }
), } catch (error) {
); console.log(showErrorMessage(`Failed to check node status: ${error.message}`));
await showNavigationMenu(); await showNavigationMenu();
} }
} catch (error) { }
console.log(
showErrorMessage(`Failed to check node status: ${error.message}`),
);
await showNavigationMenu();
}
}