nicely formatted, edge function used for tracking

This commit is contained in:
Kumaraguru 2024-12-05 02:07:41 +00:00
parent 5f5a5e1b76
commit bcabe44b43
No known key found for this signature in database
GPG Key ID: 4E4555A84ECD28F7
3 changed files with 790 additions and 243 deletions

821
index.js
View File

@ -10,6 +10,7 @@ import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';
import axios from 'axios'; import axios from 'axios';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import boxen from 'boxen';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@ -22,11 +23,11 @@ const ASCII_ART = `
<EFBFBD><EFBFBD><EFBFBD>
@ -37,6 +38,38 @@ const ASCII_ART = `
+--------------------------------------------------------------------+ +--------------------------------------------------------------------+
`; `;
function showSuccessMessage(message) {
return boxen(chalk.green(message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
title: '✅ SUCCESS',
titleAlignment: 'center'
});
}
function showErrorMessage(message) {
return boxen(chalk.red(message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
title: '❌ ERROR',
titleAlignment: 'center'
});
}
function showInfoMessage(message) {
return boxen(chalk.cyan(message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
title: ' INFO',
titleAlignment: 'center'
});
}
async function runCommand(command) { async function runCommand(command) {
try { try {
@ -58,7 +91,9 @@ async function showNavigationMenu() {
choices: [ choices: [
'1. Back to main menu', '1. Back to main menu',
'2. Exit' '2. Exit'
] ],
pageSize: 2,
loop: true
} }
]); ]);
@ -66,8 +101,7 @@ async function showNavigationMenu() {
case '1': case '1':
return main(); // Returns to main menu return main(); // Returns to main menu
case '2': case '2':
console.log(chalk.cyanBright('\nGoodbye! 👋\n')); handleExit();
process.exit(0);
} }
} }
@ -83,26 +117,102 @@ async function checkCodexInstallation() {
} }
} }
async function installCodex() { // TODO : TEST INSTALLATION TO SEE IF BACKGROUND SHELL WORKS CORRECTLY async function showPrivacyDisclaimer() {
const disclaimer = boxen(`
${chalk.yellow.bold('Privacy Disclaimer')}
Codex is currently in testnet and to make your testnet experience better, we are currently tracking some of your node and network information such as:
${chalk.cyan('- Node ID')}
${chalk.cyan('- Peer ID')}
${chalk.cyan('- Public IP address')}
${chalk.cyan('- Codex node version')}
${chalk.cyan('- Number of connected peers')}
${chalk.cyan('- Discovery and listening ports')}
These information will be used for calculating various metrics that can eventually make the Codex experience better. Please agree to the following disclaimer to continue using the Codex Storage CLI or alternatively, use the manual setup instructions at docs.codex.storage.
`, {
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'yellow',
title: '📋 IMPORTANT',
titleAlignment: 'center'
});
console.log(disclaimer);
const { agreement } = await inquirer.prompt([
{
type: 'input',
name: 'agreement',
message: 'Do you agree to the privacy disclaimer? (y/n):',
validate: (input) => {
const lowercased = input.toLowerCase();
if (lowercased === 'y' || lowercased === 'n') {
return true;
}
return 'Please enter either y or n';
}
}
]);
return agreement.toLowerCase() === 'y';
}
async function checkDependencies() {
if (platform === 'linux') {
try {
await runCommand('ldconfig -p | grep libgomp');
return true;
} catch (error) {
console.log(showErrorMessage('Required dependency libgomp1 is not installed.'));
console.log(showInfoMessage(
'For Debian-based Linux systems, please install it manually using:\n\n' +
chalk.white('sudo apt update && sudo apt install libgomp1')
));
return false;
}
}
return true;
}
async function installCodex() {
if (platform === 'win32') { if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!')); console.log(showInfoMessage('Coming soon for Windows!'));
return;
}
// Show privacy disclaimer first
const agreed = await showPrivacyDisclaimer();
if (!agreed) {
console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage'));
handleExit();
return; return;
} }
try { try {
console.log(chalk.cyanBright('Downloading Codex binaries...')); // Check dependencies only for Linux
const dependenciesInstalled = await checkDependencies();
if (!dependenciesInstalled) {
console.log(showInfoMessage('Please install the required dependencies and try again.'));
await showNavigationMenu();
return;
}
const spinner = createSpinner('Downloading Codex binaries...').start();
const downloadCommand = 'curl -# -L https://get.codex.storage/install.sh | bash'; const downloadCommand = 'curl -# -L https://get.codex.storage/install.sh | bash';
await runCommand(downloadCommand); await runCommand(downloadCommand);
console.log(chalk.green('Codex binaries downloaded')); spinner.success();
console.log(chalk.cyanBright('\nInstalling dependencies...'));
await runCommand('sudo apt update && sudo apt install libgomp1'); const version = await runCommand('\ncodex --version');
console.log(chalk.green('Dependencies installed successfully!')); console.log(showSuccessMessage(
const version = await runCommand('\ncodex --version'); 'Codex is successfully installed!\n\n' +
console.log(chalk.green('Codex is successfully installed!')); `Version: ${version}`
console.log(chalk.cyanBright(version)); ));
await showNavigationMenu(); await showNavigationMenu();
} catch (error) { } catch (error) {
console.error(chalk.red('Failed to install Codex:', error.message)); console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
await showNavigationMenu(); await showNavigationMenu();
} }
} }
@ -117,213 +227,380 @@ async function isNodeRunning() {
} }
async function runCodex() { async function runCodex() {
// Check if a Codex node is already running
const nodeAlreadyRunning = await isNodeRunning(); const nodeAlreadyRunning = await isNodeRunning();
if (nodeAlreadyRunning) { if (nodeAlreadyRunning) {
console.log(chalk.green('A Codex node is already running.')); console.log(showInfoMessage('A Codex node is already running.'));
await showNavigationMenu();
} else {
const { discPort, listenPort } = await inquirer.prompt([
{
type: 'number',
name: 'discPort',
message: 'Enter the discovery port (default is 8090):',
default: 8090
},
{
type: 'number',
name: 'listenPort',
message: 'Enter the listening port (default is 8070):',
default: 8070
}
]);
try {
const command = `codex \
--data-dir=datadir \
--disc-port=${discPort} \
--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort} \
--nat=\`curl -s https://ip.codex.storage\` \
--api-cors-origin="*" \
--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`;
console.log(showInfoMessage(
'🚀 Codex node is running...\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'
));
// Start the node
const nodeProcess = exec(command);
// Wait for node to start and get initial data
await new Promise(resolve => setTimeout(resolve, 5000));
// Log node data to Supabase
try {
const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info');
if (response.status === 200) {
await logToSupabase(response.data);
console.log(showSuccessMessage('Node data logged successfully'));
}
} catch (error) {
console.log(showErrorMessage(`Failed to log node data: ${error.message}`));
}
// Wait for node process to exit
await new Promise((resolve, reject) => {
nodeProcess.on('exit', (code) => {
if (code === 0) resolve();
else reject(new Error(`Node exited with code ${code}`));
});
});
} catch (error) {
console.log(showErrorMessage(`Failed to run Codex: ${error.message}`));
}
await showNavigationMenu(); await showNavigationMenu();
} }
else { }
const { discPort, listenPort } = await inquirer.prompt([ async function logToSupabase(nodeData) {
try {
// Ensure peerCount is at least 0 (not undefined or null)
const peerCount = nodeData.table.nodes ? nodeData.table.nodes.length : "0";
console.log("peerCount is",peerCount);
const payload = {
nodeId: nodeData.table.localNode.nodeId,
peerId: nodeData.table.localNode.peerId,
publicIp: nodeData.announceAddresses[0].split('/')[2],
version: nodeData.codex.version,
peerCount: peerCount == 0 ? "0" : peerCount,
port: nodeData.announceAddresses[0].split('/')[4],
listeningAddress: nodeData.table.localNode.address
};
console.log('Sending data to Supabase:', JSON.stringify(payload, null, 2));
const response = await axios.post('https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/codexnodes', payload, {
headers: {
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
console.log('Successfully logged to Supabase');
return true;
} else {
console.error('Unexpected response:', response.status, response.data);
return false;
}
} catch (error) {
console.error('Failed to log to Supabase:', error.message);
if (error.response) {
console.error('Error response:', {
status: error.response.status,
data: error.response.data
});
}
return false;
}
}
async function showNodeDetails(data) {
const { choice } = await inquirer.prompt([
{ {
type: 'number', type: 'list',
name: 'discPort', name: 'choice',
message: 'Enter the discovery port (default is 8090):', message: 'Select information to view:',
default: 8090 choices: [
}, '1. View Connected Peers',
{ '2. View Node Information',
type: 'number', '3. Back to Main Menu',
name: 'listenPort', '4. Exit'
message: 'Enter the listening port (default is 8070):', ],
default: 8070 pageSize: 4,
loop: true
} }
]); ]);
try { switch (choice.split('.')[0].trim()) {
const command = `codex \ case '1':
--data-dir=datadir \ const peerCount = data.table.nodes.length;
--disc-port=${discPort} \ if (peerCount > 0) {
--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort} \ console.log(showInfoMessage('Connected Peers'));
--nat=\`curl -s https://ip.codex.storage\` \ data.table.nodes.forEach((node, index) => {
--api-cors-origin="*" \ console.log(boxen(
--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`; `Peer ${index + 1}:\n` +
`${chalk.cyan('Peer ID:')} ${node.peerId}\n` +
console.log(chalk.cyanBright('\n\n Codex node is running...')); `${chalk.cyan('Address:')} ${node.address}\n` +
console.log(chalk.cyanBright('\n Please keep this terminal open. Start a new terminal to start interacting with the node')); `${chalk.cyan('Status:')} ${node.seen ? chalk.green('Active') : chalk.gray('Inactive')}`,
console.log(chalk.cyanBright('\n Press CTRL+C to stop the node')); {
await runCommand(command); padding: 1,
// TODO : HANDLE THIS CHECKING PART margin: 1,
const peerIdResponse = await runCommand('curl http://localhost:8080/api/codex/v1/peerid -w \'\\n\''); borderStyle: 'round',
console.log(chalk.green('Codex node is successfully running. Peer ID:')); borderColor: 'blue'
console.log(chalk.cyanBright(peerIdResponse.trim())); }
await showNavigationMenu(); ));
} catch (error) { });
console.error(chalk.red('Failed to run Codex:', error.message)); } else {
await showNavigationMenu(); console.log(showInfoMessage('No connected peers found.'));
}
return showNodeDetails(data);
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);
case '3':
return main();
case '4':
handleExit();
break;
} }
} }
}
async function checkNodeStatus() { async function checkNodeStatus() {
if (platform === 'win32') { if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!')); console.log(showInfoMessage('Coming soon for Windows!'));
return; return;
} }
try { try {
// Check if a Codex node is already running const nodeRunning = await isNodeRunning();
const nodeRunning = await isNodeRunning();
if (nodeRunning) { if (nodeRunning) {
const spinner = createSpinner('Checking node status...').start(); const spinner = createSpinner('Checking node status...').start();
const response = await runCommand('curl http://localhost:8080/api/codex/v1/debug/info -w \'\\n\''); const response = await runCommand('curl http://localhost:8080/api/codex/v1/debug/info -w \'\\n\'');
spinner.success(); spinner.success();
// Parse the JSON response // Parse the JSON response
const data = JSON.parse(response); const data = JSON.parse(response);
// Determine if node is online and discoverable based on the connected peers // Determine if node is online and discoverable
const peerCount = data.table.nodes.length; const peerCount = data.table.nodes.length;
const isOnline = peerCount > 2; const isOnline = peerCount > 2;
// Display node status based on connected peers // Show status banner
const statusMessage = isOnline console.log(boxen(
? chalk.bgGreen(" Node status : ONLINE & DISCOVERABLE ") isOnline
: chalk.bgRed(" Node status : OFFLINE "); ? chalk.green('Node is ONLINE & DISCOVERABLE')
const peerMessage = `Connected peers : ${peerCount}`; : chalk.yellow('Node is ONLINE but has few peers'),
{
console.log('\n' + chalk.bold.cyanBright('📊 Node Status Summary')); padding: 1,
console.log('━'.repeat(50)); margin: 1,
borderStyle: 'round',
// Version Information borderColor: isOnline ? 'green' : 'yellow',
console.log(chalk.bold.cyanBright('🔹 Version Info')); title: '🔌 Node Status',
console.log(` Version: ${data.codex.version}`); titleAlignment: 'center'
console.log(` Revision: ${data.codex.revision}\n`); }
));
// Local Node Information
console.log(chalk.bold.cyanBright('🔹 Local Node')); // Show interactive menu for details
console.log(` Node ID: ${data.table.localNode.nodeId}`); await showNodeDetails(data);
console.log(` Peer ID: ${data.table.localNode.peerId}`);
console.log(` Listening Address: ${data.table.localNode.address}\n`);
// Network Information
console.log(chalk.bold.cyanBright('🔹 Network Status'));
console.log(` Public IP: ${data.announceAddresses[0].split('/')[2]}`);
console.log(` Port: ${data.announceAddresses[0].split('/')[4]}\n`);
// Connected Peers Details
if (peerCount > 0) {
console.log(chalk.bold.cyanBright('🔹 Connected Peers'));
data.table.nodes.forEach((node, index) => {
console.log(` ${index + 1}. Peer ${chalk.cyan(node.peerId)}`);
console.log(` Address: ${node.address}`);
console.log(` Status: ${node.seen ? chalk.green('Active') : chalk.gray('Inactive')}`);
if (index < peerCount - 1) console.log(''); // Add spacing between peers
});
} else { } else {
console.log(chalk.red('No connected peers.')); console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
} }
console.log('━'.repeat(50));
await showNavigationMenu();
}
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
} catch (error) { } catch (error) {
console.error(chalk.red('Failed to check node status:', error.message)); console.log(showErrorMessage(`Failed to check node status: ${error.message}`));
await showNavigationMenu(); await showNavigationMenu();
} }
} }
// Define this function once, near the top of the file
function handleCommandLineOperation() {
return process.argv.length > 2;
}
async function uploadFile() { async function uploadFile(filePath = null) {
if (platform === 'win32') { if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!')); console.log(showInfoMessage('Coming soon for Windows!'));
return; return;
} }
const nodeRunning = await isNodeRunning(); const nodeRunning = await isNodeRunning();
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
}
if (nodeRunning) { console.log(boxen(
console.log(chalk.bgYellow('\n ⚠️ Warning: Codex does not encrypt files. Anything uploaded will be available publicly on testnet. The testnet does not provide any guarentees - please do not use in production ⚠️ \n')); chalk.yellow('⚠️ Codex does not encrypt files. Anything uploaded will be available publicly on testnet.\nThe testnet does not provide any guarantees - please do not use in production.'),
const { filePath } = await inquirer.prompt([
{ {
type: 'input', padding: 1,
name: 'filePath', margin: 1,
message: 'Enter the file path to upload:', borderStyle: 'round',
validate: input => input.length > 0 borderColor: 'yellow',
title: '⚠️ Warning',
titleAlignment: 'center'
} }
));
]);
let fileToUpload = filePath;
if (!fileToUpload) {
const { inputPath } = await inquirer.prompt([
{
type: 'input',
name: 'inputPath',
message: 'Enter the file path to upload:',
validate: input => input.length > 0
}
]);
fileToUpload = inputPath;
}
try { try {
// Check if file exists
await fs.access(fileToUpload);
const spinner = createSpinner('Uploading file').start(); const spinner = createSpinner('Uploading file').start();
// TODO : Upload along with metadata like file name, extension etc., try {
const result = await runCommand(`curl -X POST http://localhost:8080/api/codex/v1/data -H 'Content-Type: application/octet-stream' -w '\\n' -T ${filePath}`); const result = await runCommand(`curl -X POST http://localhost:8080/api/codex/v1/data -H 'Content-Type: application/octet-stream' -w '\\n' -T ${fileToUpload}`);
spinner.success(); spinner.success();
console.log(chalk.green('Successfully uploaded!')); console.log(showSuccessMessage('Successfully uploaded!\n\nCID: ' + result.trim()));
console.log(chalk.bgGreen('\nCID:', result.trim())); } catch (error) {
await showNavigationMenu(); spinner.error();
throw new Error(`Failed to upload: ${error.message}`);
}
} catch (error) { } catch (error) {
console.error(chalk.red('Failed to upload file:', error.message)); console.log(showErrorMessage(error.code === 'ENOENT'
await showNavigationMenu(); ? `File not found: ${fileToUpload}`
: `Error uploading file: ${error.message}`));
} }
}
else{ return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
} }
async function downloadFile() { function parseCommandLineArgs() {
const args = process.argv.slice(2);
if (args.length === 0) return null;
switch (args[0]) {
case '--upload':
if (args.length !== 2) {
console.log(showErrorMessage('Usage: npx codexstorage --upload <filename>'));
process.exit(1);
}
return { command: 'upload', value: args[1] };
case '--download':
if (args.length !== 2) {
console.log(showErrorMessage('Usage: npx codexstorage --download <cid>'));
process.exit(1);
}
return { command: 'download', value: args[1] };
default:
console.log(showErrorMessage(
'Invalid command. Available commands:\n\n' +
'npx codexstorage\n' +
'npx codexstorage --upload <filename>\n' +
'npx codexstorage --download <cid>'
));
process.exit(1);
}
}
async function downloadFile(cid = null) {
if (platform === 'win32') { if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!')); console.log(showInfoMessage('Coming soon for Windows!'));
return; return;
} }
const nodeRunning = await isNodeRunning(); const nodeRunning = await isNodeRunning();
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
}
if (nodeRunning) { let cidToDownload = cid;
const { cid } = await inquirer.prompt([ if (!cidToDownload) {
{ const { inputCid } = await inquirer.prompt([
type: 'input', {
name: 'cid', type: 'input',
message: 'Enter the CID:', name: 'inputCid',
validate: input => input.length > 0 message: 'Enter the CID:',
} validate: input => input.length > 0
]); }
]);
cidToDownload = inputCid;
}
try { try {
const spinner = createSpinner('Downloading file').start(); const spinner = createSpinner('Downloading file').start();
await runCommand(`curl "http://localhost:8080/api/codex/v1/data/${cid}/network/stream" -o "${cid}.png"`); try {
spinner.success(); await runCommand(`curl "http://localhost:8080/api/codex/v1/data/${cidToDownload}/network/stream" -o "${cidToDownload}.png"`);
console.log(chalk.green(`Successfully downloaded!`)); spinner.success();
console.log(chalk.bgGreen(`\nFile saved as ${cid}.png`)); console.log(showSuccessMessage(`Successfully downloaded!\n\nFile saved as ${cidToDownload}.png`));
await showNavigationMenu(); } catch (error) {
spinner.error();
throw new Error(`Failed to download: ${error.message}`);
}
} catch (error) { } catch (error) {
console.error(chalk.red('Failed to download file:', error.message)); console.log(showErrorMessage(`Error downloading file: ${error.message}`));
await showNavigationMenu();
} }
}
else{ return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
} }
async function showLocalFiles() { async function showLocalFiles() {
if (platform === 'win32') { if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!')); console.log(showInfoMessage('Coming soon for Windows!'));
return;
}
const nodeRunning = await isNodeRunning();
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
return; return;
} }
const nodeRunning = await isNodeRunning();
if (nodeRunning) {
try { try {
const spinner = createSpinner('Fetching local files...').start(); const spinner = createSpinner('Fetching local files...').start();
const filesResponse = await runCommand('curl http://localhost:8080/api/codex/v1/data -w \'\\n\''); const filesResponse = await runCommand('curl http://localhost:8080/api/codex/v1/data -w \'\\n\'');
@ -333,8 +610,7 @@ async function showLocalFiles() {
const filesData = JSON.parse(filesResponse); const filesData = JSON.parse(filesResponse);
if (filesData.content && filesData.content.length > 0) { if (filesData.content && filesData.content.length > 0) {
console.log(chalk.cyanBright('\nLocal Files:')); console.log(showInfoMessage(`Found ${filesData.content.length} local file(s)`));
console.log('━'.repeat(50));
// Iterate through each file and display information // Iterate through each file and display information
filesData.content.forEach((file, index) => { filesData.content.forEach((file, index) => {
@ -343,108 +619,171 @@ async function showLocalFiles() {
// Convert the UNIX timestamp to a readable format // Convert the UNIX timestamp to a readable format
const uploadedDate = new Date(uploadedAt * 1000).toLocaleString(); const uploadedDate = new Date(uploadedAt * 1000).toLocaleString();
const fileSize = (originalBytes / 1024).toFixed(2); // Convert to KB
console.log(`📁 File ${index + 1}:`); console.log(boxen(
console.log(` Filename : ${chalk.green(filename)}`); `${chalk.cyan('File')} ${index + 1} of ${filesData.content.length}\n\n` +
console.log(` CID : ${chalk.cyan(cid)}`); `${chalk.cyan('Filename:')} ${filename}\n` +
console.log(` Root Hash : ${chalk.cyan(rootHash)}`); `${chalk.cyan('CID:')} ${cid}\n` +
console.log(` Original Bytes : ${chalk.yellow(originalBytes)}`); `${chalk.cyan('Size:')} ${fileSize} KB\n` +
console.log(` Block Size : ${chalk.yellow(blockSize)}`); `${chalk.cyan('MIME Type:')} ${mimetype}\n` +
console.log(` Protected : ${chalk.yellow(isProtected ? 'Yes' : 'No')}`); `${chalk.cyan('Uploaded:')} ${uploadedDate}\n` +
console.log(` MIME Type : ${chalk.green(mimetype)}`); `${chalk.cyan('Protected:')} ${isProtected ? chalk.green('Yes') : chalk.red('No')}`,
console.log(` Uploaded At : ${chalk.magenta(uploadedDate)}`); {
console.log('━'.repeat(50)); padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue',
title: `📁 File Details`,
titleAlignment: 'center'
}
));
}); });
await showNavigationMenu();
} else { } else {
console.log(chalk.red('No local files found.')); console.log(showInfoMessage('No local files found.'));
await showNavigationMenu();
} }
await showNavigationMenu();
} catch (error) { } catch (error) {
console.error(chalk.red('Failed to show local files:', error.message)); console.log(showErrorMessage(`Failed to show local files: ${error.message}`));
await showNavigationMenu(); await showNavigationMenu();
} }
} }
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
}
async function uninstallCodex() { async function uninstallCodex() {
const binaryPath = '/usr/local/bin/codex'; const binaryPath = '/usr/local/bin/codex';
// Ask for confirmation
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: chalk.yellow('⚠️ Are you sure you want to uninstall Codex? This action cannot be undone.'),
default: false
}
]);
if (!confirm) {
console.log(showInfoMessage('Uninstall cancelled.'));
await showNavigationMenu();
return;
}
try { try {
console.log(`Attempting to remove Codex binary from ${binaryPath} using sudo...`); console.log(showInfoMessage(`Attempting to remove Codex binary from ${binaryPath}...`));
// Use sudo rm to delete the Codex binary
await runCommand(`sudo rm ${binaryPath}`); await runCommand(`sudo rm ${binaryPath}`);
console.log(`Codex binary removed from ${binaryPath}.`); console.log(showSuccessMessage('Codex has been successfully uninstalled.'));
await showNavigationMenu(); await showNavigationMenu();
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
console.log('Codex binary not found, nothing to uninstall.'); console.log(showInfoMessage('Codex binary not found, nothing to uninstall.'));
} else { } else {
console.error(chalk.red(`Looks like Codex is not installed yet. Please install before trying to remove the Codex binaries.`)); console.log(showErrorMessage('Failed to uninstall Codex. Please make sure Codex is installed before trying to remove it.'));
} }
await showNavigationMenu(); await showNavigationMenu();
} }
} }
// Add this function for cleanup and goodbye
function handleExit() {
console.log(boxen(
chalk.cyanBright('👋 Thank you for using Codex Storage CLI! Goodbye!'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
title: '👋 GOODBYE',
titleAlignment: 'center'
}
));
process.exit(0);
}
// Add signal handlers at the start of main
async function main() { async function main() {
// Handle command line arguments
while (true) { const commandArgs = parseCommandLineArgs();
console.log('\n' + chalk.cyanBright(ASCII_ART)); if (commandArgs) {
switch (commandArgs.command) {
const { choice } = await inquirer.prompt([ case 'upload':
{ await uploadFile(commandArgs.value);
type: 'list', return;
name: 'choice', case 'download':
message: 'Select an option:', await downloadFile(commandArgs.value);
choices: [
'1. Download and install Codex',
'2. Run Codex node',
'3. Check node status',
'4. Upload a file',
'5. Download a file',
'6. Show local data',
'7. Uninstall Codex node',
]
}
]);
if (choice.startsWith('8')) {
console.log(chalk.cyanBright('\nGoodbye! 👋\n'));
break;
}
switch (choice.split('.')[0]) {
case '1':
await checkCodexInstallation();
break;
case '2':
await runCodex();
return; return;
case '3':
await checkNodeStatus();
break;
case '4':
await uploadFile();
break;
case '5':
await downloadFile();
break;
case '6':
await showLocalFiles();
break;
case '7':
await uninstallCodex();
break;
} }
}
console.log('\n'); // Add some spacing between operations // Handle exit signals
process.on('SIGINT', handleExit);
process.on('SIGTERM', handleExit);
process.on('SIGQUIT', handleExit);
try {
while (true) {
console.log('\n' + chalk.cyanBright(ASCII_ART));
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'Select an option:',
choices: [
'1. Download and install Codex',
'2. Run Codex node',
'3. Check node status',
'4. Upload a file',
'5. Download a file',
'6. Show local data',
'7. Uninstall Codex node',
'8. Exit'
],
pageSize: 8,
loop: true
}
]).catch(() => {
handleExit();
return { choice: '8' };
});
if (choice.startsWith('8')) {
handleExit();
break;
}
switch (choice.split('.')[0]) {
case '1':
await checkCodexInstallation();
break;
case '2':
await runCodex();
return;
case '3':
await checkNodeStatus();
break;
case '4':
await uploadFile();
break;
case '5':
await downloadFile();
break;
case '6':
await showLocalFiles();
break;
case '7':
await uninstallCodex();
break;
}
console.log('\n'); // Add some spacing between operations
}
} catch (error) {
if (error.message.includes('ExitPromptError')) {
handleExit();
} else {
console.error(chalk.red('An error occurred:', error.message));
handleExit();
}
} }
} }

211
package-lock.json generated
View File

@ -1,15 +1,16 @@
{ {
"name": "codexstorage", "name": "codexstorage",
"version": "1.0.0", "version": "1.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "codexstorage", "name": "codexstorage",
"version": "1.0.0", "version": "1.0.4",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.7.7", "axios": "^1.7.7",
"boxen": "^8.0.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"inquirer": "^12.0.1", "inquirer": "^12.0.1",
"nanospinner": "^1.1.0" "nanospinner": "^1.1.0"
@ -250,6 +251,14 @@
"undici-types": "~6.19.8" "undici-types": "~6.19.8"
} }
}, },
"node_modules/ansi-align": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
"dependencies": {
"string-width": "^4.1.0"
}
},
"node_modules/ansi-escapes": { "node_modules/ansi-escapes": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@ -301,6 +310,122 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/boxen": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
"integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
"dependencies": {
"ansi-align": "^3.0.1",
"camelcase": "^8.0.0",
"chalk": "^5.3.0",
"cli-boxes": "^3.0.0",
"string-width": "^7.2.0",
"type-fest": "^4.21.0",
"widest-line": "^5.0.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/boxen/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/boxen/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/boxen/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
},
"node_modules/boxen/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/boxen/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/boxen/node_modules/type-fest": {
"version": "4.30.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz",
"integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/boxen/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/camelcase": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
"integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
@ -317,6 +442,17 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
}, },
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
"integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-width": { "node_modules/cli-width": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
@ -410,6 +546,17 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/get-east-asian-width": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
"integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -591,6 +738,66 @@
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"peer": true "peer": true
}, },
"node_modules/widest-line": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
"integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
"dependencies": {
"string-width": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/widest-line/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/widest-line/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
},
"node_modules/widest-line/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/widest-line/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",

View File

@ -15,6 +15,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.7.7", "axios": "^1.7.7",
"boxen": "^8.0.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"inquirer": "^12.0.1", "inquirer": "^12.0.1",
"nanospinner": "^1.1.0" "nanospinner": "^1.1.0"