2024-10-31 02:19:42 +00:00
#!/usr/bin/env node
import inquirer from 'inquirer' ;
import { exec } from 'child_process' ;
import { promisify } from 'util' ;
import os from 'os' ;
import { createSpinner } from 'nanospinner' ;
import chalk from 'chalk' ;
import { fileURLToPath } from 'url' ;
import { dirname } from 'path' ;
import axios from 'axios' ;
2024-10-31 15:27:33 +00:00
import * as fs from 'fs/promises' ;
2024-12-05 02:07:41 +00:00
import boxen from 'boxen' ;
2024-10-31 02:19:42 +00:00
const _ _filename = fileURLToPath ( import . meta . url ) ;
const _ _dirname = dirname ( _ _filename ) ;
const execAsync = promisify ( exec ) ;
const platform = os . platform ( ) ;
const ASCII _ART = `
█ █ █ █ █ █ ╗ █ █ █ █ █ █ ╗ █ █ █ █ █ █ ╗ █ █ █ █ █ █ █ ╗ █ █ ╗ █ █ ╗
█ █ ╔ ═ ═ ═ ═ ╝ █ █ ╔ ═ ═ ═ █ █ ╗ █ █ ╔ ═ ═ █ █ ╗ █ █ ╔ ═ ═ ═ ═ ╝ ╚ █ █ ╗ █ █ ╔ ╝
█ █ ║ █ █ ║ █ █ ║ █ █ ║ █ █ ║ █ █ █ █ █ ╗ ╚ █ █ █ ╔ ╝
█ █ ║ █ █ ║ █ █ ║ █ █ ║ █ █ ║ █ █ ╔ ═ ═ ╝ █ █ ╔ █ █ ╗
2024-12-05 02:31:04 +00:00
╚ █ █ █ █ █ █ ╗ ╚ █ █ █ █ █ █ ╔ ╝ █ █ █ █ █ █ ╔ ╝ █ █ █ █ █ █ █ █ ╗
2024-10-31 02:19:42 +00:00
╚ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ═ ╝ ╚ ═ ╝ ╚ ═ ╝
█ █ █ █ █ █ █ ╗ █ █ █ █ █ █ █ █ ╗ █ █ █ █ █ █ ╗ █ █ █ █ █ █ ╗ █ █ █ █ █ ╗ █ █ █ █ █ █ ╗ █ █ █ █ █ █ █ ╗
2024-12-05 02:07:41 +00:00
█ █ ╔ ═ ═ ═ ═ ╝ ╚ ═ ═ █ █ ╔ ═ ═ ╝ █ █ ╔ ═ ═ ═ █ █ ╗ █ █ ╔ ═ ═ █ █ ╗ █ █ ╔ ═ ═ █ █ ╗ █ █ ╔ ═ ═ ═ ═ ╝ █ ╔ ═ ═ ═ ═ ╝
2024-10-31 02:19:42 +00:00
█ █ █ █ █ █ █ ╗ █ █ ║ █ █ ║ █ █ ║ █ █ █ █ █ █ ╔ ╝ █ █ █ █ █ █ █ ║ █ █ ║ █ █ █ ╗ █ █ █ █ █ ╗
╚ ═ ═ ═ ═ █ █ ║ █ █ ║ █ █ ║ █ █ ║ █ █ ╔ ═ ═ █ █ ╗ █ █ ╔ ═ ═ █ █ ║ █ █ ║ █ █ ║ █ █ ╔ ═ ═ ╝
█ █ █ █ █ █ █ ║ █ █ ║ ╚ █ █ █ █ █ █ ╔ ╝ █ █ ║ █ █ ║ █ █ ║ █ █ ║ ╚ █ █ █ █ █ █ ╔ ╝ █ █ █ █ █ █ █ ╗
╚ ═ ═ ═ ═ ═ ═ ╝ ╚ ═ ╝ ╚ ═ ═ ═ ═ ═ ╝ ╚ ═ ╝ ╚ ═ ╝ ╚ ═ ╝ ╚ ═ ╝ ╚ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ═ ╝
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
| Docs : docs . codex . storage | Discord : discord . gg / codex - storage |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
` ;
2024-12-05 02:07:41 +00:00
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'
} ) ;
}
2024-10-31 02:19:42 +00:00
async function runCommand ( command ) {
try {
const { stdout , stderr } = await execAsync ( command ) ;
return stdout ;
} catch ( error ) {
console . error ( 'Error:' , error . message ) ;
throw error ;
}
}
async function showNavigationMenu ( ) {
console . log ( '\n' )
const { choice } = await inquirer . prompt ( [
{
type : 'list' ,
name : 'choice' ,
message : 'What would you like to do?' ,
choices : [
'1. Back to main menu' ,
'2. Exit'
2024-12-05 02:07:41 +00:00
] ,
pageSize : 2 ,
loop : true
2024-10-31 02:19:42 +00:00
}
] ) ;
switch ( choice . split ( '.' ) [ 0 ] ) {
case '1' :
return main ( ) ; // Returns to main menu
case '2' :
2024-12-05 02:07:41 +00:00
handleExit ( ) ;
2024-10-31 02:19:42 +00:00
}
}
async function checkCodexInstallation ( ) {
try {
const version = await runCommand ( 'codex --version' ) ;
console . log ( chalk . green ( 'Codex is already installed. Version:' ) ) ;
console . log ( chalk . green ( version ) ) ;
await showNavigationMenu ( ) ;
} catch ( error ) {
console . log ( chalk . cyanBright ( 'Codex is not installed, proceeding with installation...' ) ) ;
await installCodex ( ) ;
}
}
2024-12-05 02:07:41 +00:00
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 ( ) {
2024-10-31 02:19:42 +00:00
if ( platform === 'win32' ) {
2024-12-05 02:07:41 +00:00
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 ( ) ;
2024-10-31 02:19:42 +00:00
return ;
}
try {
2024-12-05 02:07:41 +00:00
// 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 ( ) ;
2024-10-31 02:19:42 +00:00
const downloadCommand = 'curl -# -L https://get.codex.storage/install.sh | bash' ;
await runCommand ( downloadCommand ) ;
2024-12-05 02:07:41 +00:00
spinner . success ( ) ;
const version = await runCommand ( '\ncodex --version' ) ;
console . log ( showSuccessMessage (
'Codex is successfully installed!\n\n' +
` Version: ${ version } `
) ) ;
await showNavigationMenu ( ) ;
2024-10-31 02:19:42 +00:00
} catch ( error ) {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( ` Failed to install Codex: ${ error . message } ` ) ) ;
2024-10-31 15:27:33 +00:00
await showNavigationMenu ( ) ;
2024-10-31 02:19:42 +00:00
}
}
async function isNodeRunning ( ) {
try {
const response = await axios . get ( 'http://localhost:8080/api/codex/v1/debug/info' ) ;
return response . status === 200 ;
} catch ( error ) {
return false ;
}
}
2024-12-09 14:17:07 +00:00
async function isCodexInstalled ( ) {
try {
await runCommand ( 'codex --version' ) ;
return true ;
} catch ( error ) {
return false ;
}
}
2024-10-31 02:19:42 +00:00
async function runCodex ( ) {
2024-12-09 14:17:07 +00:00
// First check if Codex is installed
const isInstalled = await isCodexInstalled ( ) ;
if ( ! isInstalled ) {
console . log ( showErrorMessage ( 'Codex is not installed. Please install Codex first using option 1 from the main menu.' ) ) ;
await showNavigationMenu ( ) ;
return ;
}
2024-10-31 02:19:42 +00:00
const nodeAlreadyRunning = await isNodeRunning ( ) ;
if ( nodeAlreadyRunning ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'A Codex node is already running.' ) ) ;
2024-10-31 02:19:42 +00:00
await showNavigationMenu ( ) ;
2024-12-05 02:07:41 +00:00
} 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
}
] ) ;
2024-10-31 15:27:33 +00:00
2024-12-05 02:07:41 +00:00
try {
const command = ` codex \
-- data - dir = datadir \
-- disc - port = $ { discPort } \
-- listen - addrs = / i p 4 / 0 . 0 . 0 . 0 / t c p / $ { l i s t e n P o r t } \
-- 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 ) ) ;
2024-12-05 02:31:04 +00:00
// Log node data to Supabase silently
2024-12-05 02:07:41 +00:00
try {
const response = await axios . get ( 'http://localhost:8080/api/codex/v1/debug/info' ) ;
if ( response . status === 200 ) {
await logToSupabase ( response . data ) ;
2024-12-05 02:31:04 +00:00
// Show privacy notice after successful logging
console . log ( boxen (
chalk . cyan ( 'We are logging some of your node\'s public data for improving the Codex experience' ) ,
{
padding : 1 ,
margin : 1 ,
borderStyle : 'round' ,
borderColor : 'cyan' ,
title : '🔒 Privacy Notice' ,
titleAlignment : 'center' ,
dimBorder : true
}
) ) ;
2024-12-05 02:07:41 +00:00
}
} catch ( error ) {
2024-12-05 02:31:04 +00:00
// Silently handle any logging errors
2024-12-05 02:07:41 +00:00
}
// 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 } ` ) ) ;
} ) ;
} ) ;
2024-12-05 02:31:04 +00:00
2024-12-05 02:07:41 +00:00
} catch ( error ) {
console . log ( showErrorMessage ( ` Failed to run Codex: ${ error . message } ` ) ) ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
await showNavigationMenu ( ) ;
}
}
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
async function logToSupabase ( nodeData ) {
2024-10-31 02:19:42 +00:00
try {
2024-12-05 02:07:41 +00:00
// Ensure peerCount is at least 0 (not undefined or null)
const peerCount = nodeData . table . nodes ? nodeData . table . nodes . length : "0" ;
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
} ;
const response = await axios . post ( 'https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/codexnodes' , payload , {
headers : {
'Content-Type' : 'application/json'
}
} ) ;
if ( response . status === 200 ) {
return true ;
} else {
console . error ( 'Unexpected response:' , response . status , response . data ) ;
return false ;
}
2024-10-31 02:19:42 +00:00
} catch ( error ) {
2024-12-05 02:07:41 +00:00
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 ;
2024-10-31 02:19:42 +00:00
}
}
2024-12-05 02:07:41 +00:00
async function showNodeDetails ( data ) {
const { choice } = await inquirer . prompt ( [
{
type : 'list' ,
name : 'choice' ,
message : 'Select information to view:' ,
choices : [
'1. View Connected Peers' ,
'2. View Node Information' ,
'3. Back to Main Menu' ,
'4. Exit'
] ,
pageSize : 4 ,
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 ) ;
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 ;
}
}
2024-10-31 02:19:42 +00:00
async function checkNodeStatus ( ) {
if ( platform === 'win32' ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'Coming soon for Windows!' ) ) ;
2024-10-31 02:19:42 +00:00
return ;
}
try {
2024-12-05 02:07:41 +00:00
const nodeRunning = await isNodeRunning ( ) ;
if ( nodeRunning ) {
const spinner = createSpinner ( 'Checking node status...' ) . start ( ) ;
const response = await runCommand ( 'curl http://localhost:8080/api/codex/v1/debug/info -w \'\\n\'' ) ;
spinner . success ( ) ;
// Parse the JSON response
const data = JSON . parse ( response ) ;
// Determine if node is online and discoverable
const peerCount = data . table . nodes . length ;
const isOnline = peerCount > 2 ;
// Show status banner
console . log ( boxen (
isOnline
? chalk . green ( 'Node is ONLINE & DISCOVERABLE' )
: chalk . yellow ( 'Node is ONLINE but has few peers' ) ,
{
padding : 1 ,
margin : 1 ,
borderStyle : 'round' ,
borderColor : isOnline ? 'green' : 'yellow' ,
title : '🔌 Node Status' ,
titleAlignment : 'center'
}
) ) ;
// Show interactive menu for details
await showNodeDetails ( data ) ;
2024-10-31 02:19:42 +00:00
} else {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( 'Codex node is not running. Try again after starting the node' ) ) ;
await showNavigationMenu ( ) ;
2024-10-31 02:19:42 +00:00
}
} catch ( error ) {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( ` Failed to check node status: ${ error . message } ` ) ) ;
2024-10-31 02:19:42 +00:00
await showNavigationMenu ( ) ;
}
}
2024-12-05 02:07:41 +00:00
// Define this function once, near the top of the file
function handleCommandLineOperation ( ) {
return process . argv . length > 2 ;
}
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
async function uploadFile ( filePath = null ) {
2024-10-31 02:19:42 +00:00
if ( platform === 'win32' ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'Coming soon for Windows!' ) ) ;
2024-10-31 02:19:42 +00:00
return ;
}
2024-12-05 02:07:41 +00:00
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 ( ) ;
}
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
console . log ( boxen (
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.' ) ,
2024-10-31 02:19:42 +00:00
{
2024-12-05 02:07:41 +00:00
padding : 1 ,
margin : 1 ,
borderStyle : 'round' ,
borderColor : 'yellow' ,
title : '⚠️ Warning' ,
titleAlignment : 'center'
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
) ) ;
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 ;
}
2024-10-31 02:19:42 +00:00
try {
2024-12-05 02:07:41 +00:00
// Check if file exists
await fs . access ( fileToUpload ) ;
2024-10-31 02:19:42 +00:00
const spinner = createSpinner ( 'Uploading file' ) . start ( ) ;
2024-12-05 02:07:41 +00:00
try {
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 ( ) ;
console . log ( showSuccessMessage ( 'Successfully uploaded!\n\nCID: ' + result . trim ( ) ) ) ;
} catch ( error ) {
spinner . error ( ) ;
throw new Error ( ` Failed to upload: ${ error . message } ` ) ;
}
2024-10-31 02:19:42 +00:00
} catch ( error ) {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( error . code === 'ENOENT'
? ` File not found: ${ fileToUpload } `
: ` Error uploading file: ${ error . message } ` ) ) ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
return handleCommandLineOperation ( ) ? process . exit ( 0 ) : showNavigationMenu ( ) ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
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 ) ;
}
2024-10-31 03:12:40 +00:00
}
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
async function downloadFile ( cid = null ) {
2024-10-31 02:19:42 +00:00
if ( platform === 'win32' ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'Coming soon for Windows!' ) ) ;
2024-10-31 02:19:42 +00:00
return ;
}
2024-10-31 03:12:40 +00:00
const nodeRunning = await isNodeRunning ( ) ;
2024-12-05 02:07:41 +00:00
if ( ! nodeRunning ) {
console . log ( showErrorMessage ( 'Codex node is not running. Try again after starting the node' ) ) ;
return handleCommandLineOperation ( ) ? process . exit ( 1 ) : showNavigationMenu ( ) ;
}
2024-10-31 03:12:40 +00:00
2024-12-05 02:07:41 +00:00
let cidToDownload = cid ;
if ( ! cidToDownload ) {
const { inputCid } = await inquirer . prompt ( [
{
type : 'input' ,
name : 'inputCid' ,
message : 'Enter the CID:' ,
validate : input => input . length > 0
}
] ) ;
cidToDownload = inputCid ;
}
2024-10-31 02:19:42 +00:00
try {
const spinner = createSpinner ( 'Downloading file' ) . start ( ) ;
2024-12-05 02:07:41 +00:00
try {
await runCommand ( ` curl "http://localhost:8080/api/codex/v1/data/ ${ cidToDownload } /network/stream" -o " ${ cidToDownload } .png" ` ) ;
spinner . success ( ) ;
console . log ( showSuccessMessage ( ` Successfully downloaded! \n \n File saved as ${ cidToDownload } .png ` ) ) ;
} catch ( error ) {
spinner . error ( ) ;
throw new Error ( ` Failed to download: ${ error . message } ` ) ;
}
2024-10-31 02:19:42 +00:00
} catch ( error ) {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( ` Error downloading file: ${ error . message } ` ) ) ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
return handleCommandLineOperation ( ) ? process . exit ( 0 ) : showNavigationMenu ( ) ;
2024-10-31 03:12:40 +00:00
}
2024-10-31 02:19:42 +00:00
async function showLocalFiles ( ) {
if ( platform === 'win32' ) {
2024-12-05 02:07:41 +00:00
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 ( ) ;
2024-10-31 02:19:42 +00:00
return ;
}
try {
const spinner = createSpinner ( 'Fetching local files...' ) . start ( ) ;
const filesResponse = await runCommand ( 'curl http://localhost:8080/api/codex/v1/data -w \'\\n\'' ) ;
spinner . success ( ) ;
// Parse the JSON response
const filesData = JSON . parse ( filesResponse ) ;
if ( filesData . content && filesData . content . length > 0 ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( ` Found ${ filesData . content . length } local file(s) ` ) ) ;
2024-10-31 02:19:42 +00:00
// Iterate through each file and display information
filesData . content . forEach ( ( file , index ) => {
const { cid , manifest } = file ;
const { rootHash , originalBytes , blockSize , protected : isProtected , filename , mimetype , uploadedAt } = manifest ;
// Convert the UNIX timestamp to a readable format
const uploadedDate = new Date ( uploadedAt * 1000 ) . toLocaleString ( ) ;
2024-12-05 02:07:41 +00:00
const fileSize = ( originalBytes / 1024 ) . toFixed ( 2 ) ; // Convert to KB
console . log ( boxen (
` ${ chalk . cyan ( 'File' ) } ${ index + 1 } of ${ filesData . content . length } \n \n ` +
` ${ chalk . cyan ( 'Filename:' ) } ${ filename } \n ` +
` ${ chalk . cyan ( 'CID:' ) } ${ cid } \n ` +
` ${ chalk . cyan ( 'Size:' ) } ${ fileSize } KB \n ` +
` ${ chalk . cyan ( 'MIME Type:' ) } ${ mimetype } \n ` +
` ${ chalk . cyan ( 'Uploaded:' ) } ${ uploadedDate } \n ` +
` ${ chalk . cyan ( 'Protected:' ) } ${ isProtected ? chalk . green ( 'Yes' ) : chalk . red ( 'No' ) } ` ,
{
padding : 1 ,
margin : 1 ,
borderStyle : 'round' ,
borderColor : 'blue' ,
title : ` 📁 File Details ` ,
titleAlignment : 'center'
}
) ) ;
2024-10-31 02:19:42 +00:00
} ) ;
} else {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'No local files found.' ) ) ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
await showNavigationMenu ( ) ;
2024-10-31 02:19:42 +00:00
} catch ( error ) {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( ` Failed to show local files: ${ error . message } ` ) ) ;
2024-10-31 02:19:42 +00:00
await showNavigationMenu ( ) ;
}
}
2024-10-31 15:27:33 +00:00
async function uninstallCodex ( ) {
const binaryPath = '/usr/local/bin/codex' ;
2024-12-05 02:07:41 +00:00
// 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 ;
}
2024-10-31 15:27:33 +00:00
try {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( ` Attempting to remove Codex binary from ${ binaryPath } ... ` ) ) ;
2024-10-31 15:27:33 +00:00
await runCommand ( ` sudo rm ${ binaryPath } ` ) ;
2024-12-05 02:07:41 +00:00
console . log ( showSuccessMessage ( 'Codex has been successfully uninstalled.' ) ) ;
2024-10-31 15:27:33 +00:00
await showNavigationMenu ( ) ;
} catch ( error ) {
if ( error . code === 'ENOENT' ) {
2024-12-05 02:07:41 +00:00
console . log ( showInfoMessage ( 'Codex binary not found, nothing to uninstall.' ) ) ;
2024-10-31 15:27:33 +00:00
} else {
2024-12-05 02:07:41 +00:00
console . log ( showErrorMessage ( 'Failed to uninstall Codex. Please make sure Codex is installed before trying to remove it.' ) ) ;
2024-10-31 15:27:33 +00:00
}
await showNavigationMenu ( ) ;
}
}
2024-12-05 02:07:41 +00:00
// 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
2024-10-31 02:19:42 +00:00
async function main ( ) {
2024-12-05 02:07:41 +00:00
// Handle command line arguments
const commandArgs = parseCommandLineArgs ( ) ;
if ( commandArgs ) {
switch ( commandArgs . command ) {
case 'upload' :
await uploadFile ( commandArgs . value ) ;
return ;
case 'download' :
await downloadFile ( commandArgs . value ) ;
return ;
}
}
// Handle exit signals
process . on ( 'SIGINT' , handleExit ) ;
process . on ( 'SIGTERM' , handleExit ) ;
process . on ( 'SIGQUIT' , handleExit ) ;
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
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' } ;
} ) ;
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
if ( choice . startsWith ( '8' ) ) {
handleExit ( ) ;
break ;
2024-10-31 02:19:42 +00:00
}
2024-12-05 02:07:41 +00:00
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 ;
}
2024-10-31 02:19:42 +00:00
2024-12-05 02:07:41 +00:00
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 ( ) ;
2024-10-31 02:19:42 +00:00
}
}
}
// Run the CLI
main ( ) . catch ( console . error ) ;