polaris-cli-tool
Version:
Polaris CLI - Modern Development Workspace Manager for Distributed Compute Resources
1,361 lines (1,210 loc) • 41.7 kB
JavaScript
import boxen from 'boxen';
import chalk from 'chalk';
import SingleBar from 'cli-progress';
import Table from 'cli-table3';
import { Command } from 'commander';
import figlet from 'figlet';
import fs, { readFileSync } from 'fs';
import gradient from 'gradient-string';
import inquirer from 'inquirer';
import fetch from 'node-fetch';
import ora from 'ora';
import path, { dirname } from 'path';
import { Client } from 'ssh2';
import { fileURLToPath } from 'url';
import xlsx from 'xlsx';
// Resolve __filename and __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Read version from package.json
const packageJsonPath = path.resolve(__dirname, 'package.json');
const { version } = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
// Initialize commander
const program = new Command();
program
.name('polaris')
.description('Polaris CLI - Modern Development Workspace Manager')
.version(version, '-v, --version', 'Output the current version');
// Display ASCII art title
function displayTitle() {
const subtitle = `
╭──────────────────────────────────────────────╮
│ ⚡ DISTRIBUTED COMPUTE SUBNET MANAGER │
╰──────────────────────────────────────────────╯`;
console.log('\n');
console.log(
gradient('#00ff87', '#00ffff', '#0099ff').multiline(
figlet.textSync('POLARIS', {
font: 'ANSI Shadow',
horizontalLayout: 'full',
width: 130,
whitespaceBreak: true,
})
)
);
console.log(gradient.mind(subtitle));
}
function transformComputeResource(resource) {
const baseResource = {
id: resource.id,
resource_type: resource.resource_type,
location: resource.location,
network: {
internal_ip: resource.internal_ip,
ssh: resource.ssh,
open_ports: resource.open_ports
? resource.open_ports.split(',').map(p => p.trim())
: [], // Fallback to empty array if undefined
},
hourly_price: resource.hourly_price,
ram: resource.ram,
storage: {
type: resource.storage_type,
capacity: resource.storage_capacity,
read_speed: resource.storage_read_speed,
write_speed: resource.storage_write_speed,
},
};
if (resource.resource_type.toUpperCase() === 'CPU') {
return {
...baseResource,
cpu_specs: {
op_modes: resource.cpu_op_modes,
address_sizes: resource.cpu_address_sizes,
byte_order: resource.cpu_byte_order,
total_cpus: resource.total_cpus,
online_cpus: resource.online_cpus,
vendor_id: resource.vendor_id,
cpu_name: resource.cpu_name,
cpu_family: resource.cpu_family,
model: resource.model,
threads_per_core: resource.threads_per_core,
cores_per_socket: resource.cores_per_socket,
sockets: resource.sockets,
stepping: resource.stepping,
cpu_max_mhz: resource.cpu_max_mhz,
cpu_min_mhz: resource.cpu_min_mhz,
},
};
} else if (resource.resource_type.toUpperCase() === 'GPU') {
return {
...baseResource,
gpu_specs: {
gpu_name: resource.gpu_name,
memory_size: resource.memory_size,
gpu_cores: resource.gpu_cores,
clock_speed: resource.clock_speed,
cuda_cores: resource.cuda_cores,
tensor_cores: resource.tensor_cores,
power_consumption: resource.power_consumption,
},
};
}
}
// Validate resources
function validateResources(resources) {
return resources.every((resource) => {
if (
resource.resource_type.toUpperCase() === 'GPU' &&
!resource.gpu_specs
) {
console.log(
chalk.red(`Error: GPU resource ${resource.id} missing GPU specifications`)
);
return false;
}
if (
resource.resource_type.toUpperCase() === 'CPU' &&
!resource.cpu_specs
) {
console.log(
chalk.red(`Error: CPU resource ${resource.id} missing CPU specifications`)
);
return false;
}
return true;
});
}
async function addComputeToMiner() {
console.log('\n' + chalk.cyan.bold('💻 Add Compute Resources to Existing Miner') + '\n');
// Get miner ID
const { minerId } = await inquirer.prompt([
{
type: 'input',
name: 'minerId',
message: chalk.cyan('🔑 Enter your Miner ID:'),
validate: (input) => (input.trim() ? true : 'Miner ID is required'),
}
]);
// Display compute resource template
console.log('\n' + chalk.cyan.bold('📋 Compute Resource Template') + '\n');
const table = new Table({
head: [
chalk.cyan.bold('Field'),
chalk.cyan.bold('Type'),
chalk.cyan.bold('Required For'),
chalk.cyan.bold('Description')
],
style: { head: [], border: ['gray'] },
});
table.push(
['id', 'String', 'ALL', 'Unique identifier'],
['resource_type', 'CPU/GPU', 'ALL', 'Type of resource'],
['location', 'String', 'ALL', 'Resource location'],
['internal_ip', 'String', 'ALL', 'Internal IP address'],
['ssh', 'String', 'ALL', 'SSH connection string'],
['open_ports', 'String', 'ALL', 'Comma-separated ports'],
['hourly_price', 'Number', 'ALL', 'Price per hour'],
['ram', 'String', 'ALL', 'RAM specification'],
['storage_type', 'NVME/SSD/HDD', 'ALL', 'Storage type'],
['storage_capacity', 'String', 'ALL', 'Storage capacity'],
['storage_read_speed', 'String', 'ALL', 'Read speed'],
['storage_write_speed', 'String', 'ALL', 'Write speed']
);
console.log(
boxen(table.toString(), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
})
);
// Get Excel file
const { filePath } = await inquirer.prompt([
{
type: 'input',
name: 'filePath',
message: chalk.cyan('\n📂 Drag and drop your compute resources Excel file here (or enter path): '),
validate: (input) => {
const trimmedInput = input.trim().replace(/^"+|"+$/g, '');
const normalizedPath = path.normalize(trimmedInput);
return fs.existsSync(normalizedPath) && normalizedPath.endsWith('.xlsx')
? true
: chalk.red('Please provide a valid Excel file (.xlsx)');
},
}
]);
const cleanPath = filePath.trim().replace(/^["']|["']$/g, '');
const normalizedPath = path.normalize(cleanPath);
// Process Excel file
const workbook = xlsx.readFile(normalizedPath);
const sheetName = workbook.SheetNames[0];
const computeResources = xlsx.utils.sheet_to_json(workbook.Sheets[sheetName]);
// Transform resources
const transformedResources = computeResources.map(resource =>
transformComputeResource(resource)
).filter(Boolean);
// Display submission for verification
console.log('\n' + chalk.cyan.bold('📝 Please verify the resources to add:') + '\n');
console.log(
boxen(JSON.stringify(transformedResources, null, 2), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
})
);
// Confirmation
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: chalk.cyan('Do you want to add these compute resources?'),
default: false,
}
]);
if (confirm) {
const spinner = ora('Validating resources...').start();
if (!validateResources(transformedResources)) {
spinner.fail('Resource validation failed');
return null;
}
spinner.text = 'Adding compute resources...';
try {
const response = await fetch(`https://orchestrator-gekh.onrender.com/api/v1/miners/${minerId}/resources/bulk`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(transformedResources),
});
if (!response.ok) {
const errorData = await response.json();
spinner.fail(chalk.red(`Failed to add resources: ${JSON.stringify(errorData)}`));
return null;
}
const result = await response.json();
spinner.succeed(chalk.green('Resources added successfully!'));
// Display the response
console.log('\n' + chalk.cyan.bold('📡 Addition Status') + '\n');
const responseBox = boxen(
`${chalk.green.bold('✨ ' + result.message)}\n\n` +
`${chalk.blue.bold('Miner ID:')} ${chalk.yellow(result.miner_id)}\n\n` +
`${chalk.blue.bold('Added Resources:')}\n${result.added_resources
.map((id) => chalk.cyan(`• ${id}`))
.join('\n')}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
title: '🎉 Resources Added',
titleAlignment: 'center',
}
);
console.log(responseBox);
return result;
} catch (error) {
spinner.fail(chalk.red(`Error adding resources: ${error.message}`));
console.log(chalk.yellow('\nTip: Make sure the API server is running at https://orchestrator-gekh.onrender.com'));
return null;
}
} else {
console.log(chalk.yellow('Operation cancelled.'));
return null;
}
}
// Display file info in workspace format
function displayFileInfo(
originalPath,
destinationPath,
status = 'PROCESSING',
resourceCount = null
) {
const fileStats = fs.statSync(originalPath);
const fileName = path.basename(originalPath);
const fileSize = (fileStats.size / (1024 * 1024)).toFixed(2);
const fileId = Math.random().toString(36).substring(2, 15);
const info = [
chalk.bold('Workspace Info\n'),
`Name ${chalk.white(fileName)}`,
`ID ${chalk.cyan(fileId)}`,
`Size ${chalk.yellow(fileSize + ' MB')}`,
`State ${
status === 'PROCESSING' ? chalk.yellow(status) : chalk.green(status)
}`,
`Location ${chalk.white(destinationPath)}`,
`Created ${chalk.white(new Date().toISOString())}`,
];
if (resourceCount !== null) {
info.push(`Resources ${chalk.yellow(resourceCount + ' compute units')}`);
}
console.log('\n');
console.log(
boxen(info.join('\n'), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'white',
})
);
return fileId;
}
async function registerMiner() {
console.log('\n' + chalk.cyan.bold('⛏️ Miner Registration') + '\n');
// Basic miner info collection
const minerInfo = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: chalk.cyan('Enter miner name:'),
validate: (input) => (input.trim() ? true : 'Name is required'),
},
{
type: 'input',
name: 'location',
message: chalk.cyan('Enter location (e.g., NYC):'),
validate: (input) => (input.trim() ? true : 'Location is required'),
},
{
type: 'input',
name: 'description',
message: chalk.cyan('Enter cluster description:'),
validate: (input) => (input.trim() ? true : 'Description is required'),
}
]);
// Display compute resource template
console.log('\n' + chalk.cyan.bold('📋 Compute Resource Template') + '\n');
const table = new Table({
head: [
chalk.cyan.bold('Field'),
chalk.cyan.bold('Type'),
chalk.cyan.bold('Required For'),
chalk.cyan.bold('Description')
],
style: { head: [], border: ['gray'] },
});
table.push(
['id', 'String', 'ALL', 'Unique identifier'],
['resource_type', 'CPU/GPU', 'ALL', 'Type of resource'],
['location', 'String', 'ALL', 'Resource location'],
['internal_ip', 'String', 'ALL', 'Internal IP address'],
['ssh', 'String', 'ALL', 'SSH connection string'],
['open_ports', 'String', 'ALL', 'Comma-separated ports'],
['hourly_price', 'Number', 'ALL', 'Price per hour'],
['ram', 'String', 'ALL', 'RAM specification'],
['storage_type', 'NVME/SSD/HDD', 'ALL', 'Storage type'],
['storage_capacity', 'String', 'ALL', 'Storage capacity'],
['storage_read_speed', 'String', 'ALL', 'Read speed'],
['storage_write_speed', 'String', 'ALL', 'Write speed'],
['cpu_specs.*', 'Object', 'CPU only', 'CPU specifications'],
['gpu_specs.*', 'Object', 'GPU only', 'GPU specifications']
);
console.log(
boxen(table.toString(), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
})
);
// Excel file collection
const { filePath } = await inquirer.prompt([
{
type: 'input',
name: 'filePath',
message: chalk.cyan('\n📂 Drag and drop your compute resources Excel file here (or enter path): '),
validate: (input) => {
const trimmedInput = input.trim().replace(/^"+|"+$/g, '');
const normalizedPath = path.normalize(trimmedInput);
return fs.existsSync(normalizedPath) && normalizedPath.endsWith('.xlsx')
? true
: chalk.red('Please provide a valid Excel file (.xlsx)');
},
}
]);
// Clean up the path
const cleanPath = filePath.trim().replace(/^["']|["']$/g, '');
const normalizedPath = path.normalize(cleanPath);
// Process Excel file
const workbook = xlsx.readFile(normalizedPath);
const sheetName = workbook.SheetNames[0];
const computeResources = xlsx.utils.sheet_to_json(workbook.Sheets[sheetName]);
// Transform resources
const transformedResources = computeResources.map(resource => {
const baseResource = {
id: resource.id,
resource_type: resource.resource_type,
location: resource.location,
network: {
internal_ip: resource.internal_ip,
ssh: resource.ssh,
password: resource.password,
open_ports: resource.open_ports.split(',').map(p => p.trim())
},
hourly_price: resource.hourly_price,
ram: resource.ram,
storage: {
type: resource.storage_type,
capacity: resource.storage_capacity,
read_speed: resource.storage_read_speed,
write_speed: resource.storage_write_speed
}
};
if (resource.resource_type.toUpperCase() === 'CPU') {
return {
...baseResource,
cpu_specs: {
op_modes: resource.cpu_op_modes,
address_sizes: resource.cpu_address_sizes,
byte_order: resource.cpu_byte_order,
total_cpus: parseInt(resource.total_cpus),
online_cpus: resource.online_cpus,
vendor_id: resource.vendor_id,
cpu_name: resource.cpu_name,
cpu_family: parseInt(resource.cpu_family),
model: parseInt(resource.model),
threads_per_core: parseInt(resource.threads_per_core),
cores_per_socket: parseInt(resource.cores_per_socket),
sockets: parseInt(resource.sockets),
stepping: resource.stepping ? parseInt(resource.stepping) : null,
cpu_max_mhz: parseFloat(resource.cpu_max_mhz),
cpu_min_mhz: parseFloat(resource.cpu_min_mhz)
}
};
} else if (resource.resource_type.toUpperCase() === 'GPU') {
return {
...baseResource,
gpu_specs: {
gpu_name: resource.gpu_name,
memory_size: resource.memory_size,
cuda_cores: resource.cuda_cores ? parseInt(resource.cuda_cores) : null,
clock_speed: resource.clock_speed,
power_consumption: resource.power_consumption
}
};
}
return null;
}).filter(Boolean);
// Construct final submission object
const submission = {
name: minerInfo.name,
location: minerInfo.location,
description: minerInfo.description,
compute_resources: transformedResources
};
// Display submission for verification
console.log('\n' + chalk.cyan.bold('📝 Please verify your submission:') + '\n');
console.log(
boxen(JSON.stringify(submission, null, 2), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
})
);
// Confirmation
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: chalk.cyan('Do you want to submit this registration?'),
default: false,
}
]);
if (confirm) {
const spinner = ora('Validating resources...').start();
// Validate resources
for (const resource of transformedResources) {
// Validate network configuration
if (!resource.network || !resource.network.internal_ip || !resource.network.ssh || !resource.network.open_ports) {
spinner.fail('Network configuration is missing or invalid');
return null;
}
// Validate resource type specific configurations
if (resource.resource_type === 'CPU' && !resource.cpu_specs) {
spinner.fail(`CPU resource ${resource.id} missing CPU specifications`);
return null;
}
if (resource.resource_type === 'GPU' && !resource.gpu_specs) {
spinner.fail(`GPU resource ${resource.id} missing GPU specifications`);
return null;
}
}
spinner.text = 'Submitting registration...';
try {
const response = await fetch('https://orchestrator-gekh.onrender.com/api/v1/miners/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!response.ok) {
const errorData = await response.json();
spinner.fail(chalk.red(`Registration failed: ${JSON.stringify(errorData)}`));
return null;
}
const result = await response.json();
spinner.succeed(chalk.green('Registration submitted successfully!'));
// Display response
console.log('\n' + chalk.cyan.bold('📡 Registration Status') + '\n');
const responseBox = boxen(
`${chalk.green.bold('✨ ' + result.message)}\n\n` +
`${chalk.blue.bold('Miner ID:')} ${chalk.yellow(result.miner_id)}\n\n` +
`${chalk.blue.bold('Compute Resources:')}\n${result.compute_resources
.map((id) => chalk.cyan(`• ${id}`))
.join('\n')}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
title: '🎉 Registration Complete',
titleAlignment: 'center',
}
);
console.log(responseBox);
// Save confirmation
console.log(
boxen(
chalk.yellow.bold('Important: Save your Miner ID\n\n') +
`Your Miner ID is: ${chalk.cyan.bold(result.miner_id)}\n` +
chalk.gray('You will need this ID to manage your compute resources.'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
title: '🔑 Keep Safe',
titleAlignment: 'center',
}
)
);
return result;
} catch (error) {
spinner.fail(chalk.red(`Error submitting registration: ${error.message}`));
console.log(chalk.yellow('\nTip: Make sure the API server is running at https://orchestrator-gekh.onrender.com'));
return null;
}
} else {
console.log(chalk.yellow('Registration cancelled.'));
return null;
}
}
// Process compute resources with dynamic loading
async function processComputeResources(filePath) {
const spinner = ora({
text: 'Initializing compute resource processing...',
spinner: 'dots12',
color: 'cyan',
}).start();
const uploadDir = path.join(__dirname, 'uploaded_resources');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
const fileName = `compute_${Date.now()}_${path.basename(filePath)}`;
const destinationPath = path.join(uploadDir, fileName);
try {
spinner.text = 'Copying resource file...';
fs.copyFileSync(filePath, destinationPath);
spinner.succeed('File copied successfully');
const fileId = displayFileInfo(filePath, destinationPath);
const multibar = new SingleBar.MultiBar({
clearOnComplete: false,
hideCursor: true,
format: '{bar} | {percentage}% | {task}',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
barsize: 30,
});
const steps = [
{ task: 'Validating resource specifications', duration: 1500 },
{ task: 'Processing compute capabilities ', duration: 2000 },
{ task: 'Configuring network settings ', duration: 1000 },
{ task: 'Establishing secure connection ', duration: 800 },
];
const bars = steps.map((step) =>
multibar.create(100, 0, { task: step.task })
);
for (let i = 0; i < steps.length; i++) {
for (let progress = 0; progress <= 100; progress += 2) {
bars[i].update(progress);
await new Promise((r) => setTimeout(r, steps[i].duration / 50));
}
bars[i].update(100);
if (i < steps.length - 1) {
await new Promise((r) => setTimeout(r, 200));
}
}
multibar.stop();
const workbook = xlsx.readFile(destinationPath);
const sheetName = workbook.SheetNames[0];
const data = xlsx.utils.sheet_to_json(workbook.Sheets[sheetName]);
displayFileInfo(destinationPath, destinationPath, 'RUNNING', data.length);
const forwardSpinner = ora({
text: 'Forwarding compute node port to 6300...',
spinner: 'dots12',
color: 'cyan',
}).start();
await new Promise((r) => setTimeout(r, 1500));
forwardSpinner.succeed('Port forwarded successfully');
const browserBar = new SingleBar.SingleBar({
format: 'Opening browser {bar} {percentage}% | {value}/{total}s',
barCompleteChar: '█',
barIncompleteChar: '░',
barsize: 30,
});
browserBar.start(100, 0);
for (let i = 0; i <= 100; i += 2) {
browserBar.update(i);
await new Promise((r) => setTimeout(r, 30));
}
browserBar.stop();
return data;
} catch (error) {
spinner.fail(chalk.red(`Error: ${error.message}`));
return null;
}
}
async function connectSSH(host, username, password = null) {
return new Promise((resolve, reject) => {
const conn = new Client();
conn.on('ready', () => {
resolve(conn);
}).on('error', (err) => {
reject(err);
});
// Connection config
const config = {
host: host,
username: username,
password: password,
// Add key-based auth support
tryKeyboard: true,
keepaliveInterval: 10000
};
conn.connect(config);
});
}
async function executeCommand(conn, command) {
return new Promise((resolve, reject) => {
conn.exec(command, (err, stream) => {
if (err) reject(err);
let output = '';
stream.on('data', (data) => {
output += data;
});
stream.on('end', () => {
resolve(output);
});
stream.on('error', (err) => {
reject(err);
});
});
});
}
async function runSubnet() {
// Get miner ID
const { minerId } = await inquirer.prompt([
{
type: 'input',
name: 'minerId',
message: chalk.cyan('🔑 Enter your Miner ID:'),
prefix: '→',
validate: (input) => {
if (!input.trim()) {
return chalk.red('Miner ID is required');
}
return true;
},
}
]);
const spinner = ora({
text: '🚀 Initializing Polaris Subnet...',
spinner: 'dots12',
color: 'cyan',
}).start();
try {
// Fetch miner data
const response = await fetch(
`https://orchestrator-gekh.onrender.com/api/v1/miners/${minerId}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
}
);
if (!response.ok) {
spinner.fail(chalk.red('Failed to fetch miner data.'));
console.log(chalk.red(`Error: ${response.statusText}`));
return;
}
const minerData = await response.json();
spinner.succeed(chalk.green('✨ Subnet initialized successfully!'));
console.log(
boxen(
`${chalk.cyan('Network Status')}\n\n` +
`${chalk.blue('●')} Miner ID: ${chalk.cyan(minerId)}\n` +
`${chalk.blue('●')} Active Nodes: ${chalk.green(minerData.compute_resources.length)}\n` +
`${chalk.blue('●')} Network Health: ${chalk.green('98.5%')}`,
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
}
)
);
// Prepare choices for resource selection
const choices = minerData.compute_resources.map(resource => ({
name: `${chalk.blue(resource.id)} → ${chalk.yellow(resource.resource_type)} [${chalk.green(resource.location)}]`,
value: resource
}));
// Resource selection
const { selectedResource } = await inquirer.prompt([
{
type: 'list',
name: 'selectedResource',
message: chalk.cyan('Select a compute resource to manage:'),
choices: choices,
pageSize: 10
}
]);
// Action selection
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: chalk.cyan(`Select action for ${chalk.yellow(selectedResource.id)}:`),
choices: [
{ name: '🚀 Run Resource', value: 'run' },
{ name: '📊 View Logs', value: 'logs' }
]
}
]);
const sshSpinner = ora({
text: `Establishing SSH connection to ${selectedResource.network.ssh}...`,
spinner: 'dots12',
color: 'cyan',
}).start();
try {
// Parse SSH URL
const sshUrl = new URL(selectedResource.network.ssh);
const host = sshUrl.hostname;
const username = sshUrl.username;
let password;
// Check if password is required
if (!selectedResource.network.ssh.includes('id_rsa')) {
sshSpinner.stop();
const { sshPassword } = await inquirer.prompt([
{
type: 'password',
name: 'sshPassword',
message: chalk.cyan(`Enter SSH password for ${username}@${host}:`),
mask: '*'
}
]);
password = sshPassword;
sshSpinner.start();
}
// Establish SSH connection
const conn = await connectSSH(host, username, password);
sshSpinner.succeed('SSH connection established');
if (action === 'run') {
const runSpinner = ora({
text: 'Starting compute resource...',
spinner: 'dots12',
color: 'cyan',
}).start();
try {
// Check system resources
const cpuInfo = await executeCommand(conn, 'cat /proc/cpuinfo | grep "model name" | head -n1');
const memInfo = await executeCommand(conn, 'free -h');
const diskInfo = await executeCommand(conn, 'df -h /');
runSpinner.succeed('Resource information retrieved');
console.log(
boxen(
`${chalk.green.bold('✨ Resource Status')}\n\n` +
`${chalk.blue('●')} ID: ${chalk.cyan(selectedResource.id)}\n` +
`${chalk.blue('●')} Type: ${chalk.yellow(selectedResource.resource_type)}\n` +
`${chalk.blue('●')} CPU: ${chalk.white(cpuInfo.split(':')[1].trim())}\n` +
`${chalk.blue('●')} Status: ${chalk.green('Connected')}\n\n` +
chalk.gray('System Information:\n') +
chalk.white(memInfo) + '\n' +
chalk.white(diskInfo),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
title: '🖥️ Compute Resource',
titleAlignment: 'center',
}
)
);
// Interactive shell option
const { startShell } = await inquirer.prompt([
{
type: 'confirm',
name: 'startShell',
message: chalk.cyan('Would you like to start an interactive shell?'),
default: false
}
]);
if (startShell) {
conn.shell((err, stream) => {
if (err) throw err;
// Handle shell input/output
stream.on('data', (data) => {
process.stdout.write(data);
});
stream.stderr.on('data', (data) => {
process.stderr.write(data);
});
stream.on('close', () => {
console.log(chalk.yellow('\nSSH connection closed'));
conn.end();
});
// Pipe process stdin to stream
process.stdin.pipe(stream);
});
} else {
conn.end();
}
} catch (error) {
runSpinner.fail(chalk.red(`Failed to execute commands: ${error.message}`));
conn.end();
}
} else if (action === 'logs') {
const logSpinner = ora({
text: 'Fetching Docker information...',
spinner: 'dots12',
color: 'cyan',
}).start();
try {
// Fetch Docker images
const dockerImages = await executeCommand(conn, 'docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"');
const imageLines = dockerImages.trim().split('\n');
// Fetch Docker containers
const dockerContainers = await executeCommand(conn, 'docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"');
const containerLines = dockerContainers.trim().split('\n');
logSpinner.succeed('Docker information retrieved successfully');
// Display Docker Images in a table
const imageTable = new Table({
head: [chalk.cyan('Repository'), chalk.cyan('Tag'), chalk.cyan('Size')],
style: { head: [], border: [] }
});
// First line is the header, skip it
for (let i = 1; i < imageLines.length; i++) {
const [repo, tag, size] = imageLines[i].split('\t');
imageTable.push([chalk.green(repo), chalk.yellow(tag), chalk.blue(size)]);
}
console.log(
boxen(
chalk.bold('🛠️ Docker Images') + '\n\n' + imageTable.toString(),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue',
title: '📦 Docker Images',
titleAlignment: 'center'
}
)
);
// Display Docker Containers in a table
const containerTable = new Table({
head: [chalk.cyan('Name'), chalk.cyan('Image'), chalk.cyan('Status'), chalk.cyan('Ports')],
style: { head: [], border: [] }
});
// First line is the header, skip it
for (let i = 1; i < containerLines.length; i++) {
const [name, image, status, ports] = containerLines[i].split('\t');
containerTable.push([chalk.green(name), chalk.yellow(image), chalk.white(status), chalk.magenta(ports)]);
}
console.log(
boxen(
chalk.bold('📡 Running Containers') + '\n\n' + containerTable.toString(),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
title: '🌐 Containers',
titleAlignment: 'center'
}
)
);
conn.end();
} catch (error) {
logSpinner.fail(chalk.red(`Failed to retrieve Docker information: ${error.message}`));
conn.end();
}
}
} catch (error) {
sshSpinner.fail(chalk.red(`SSH connection failed: ${error.message}`));
}
} catch (error) {
spinner.fail(chalk.red(`Failed to initialize subnet: ${error.message}`));
console.log(chalk.yellow('\nTip: Ensure the API server is running at https://orchestrator-gekh.onrender.com'));
}
}
// View miner compute resources
async function viewMinerCompute() {
console.log('\n' + chalk.cyan.bold('👀 View Miner Compute Resources') + '\n');
const { minerId } = await inquirer.prompt([
{
type: 'input',
name: 'minerId',
message: chalk.cyan('🔑 Enter the Miner ID:'),
validate: (input) => {
if (!input.trim()) {
return chalk.red('Miner ID is required');
}
return true;
},
},
]);
const spinner = ora({
text: 'Fetching miner data...',
spinner: 'dots12',
color: 'cyan',
}).start();
try {
const response = await fetch(
`https://orchestrator-gekh.onrender.com/api/v1/miners/${minerId}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
spinner.fail(chalk.red('Failed to fetch miner data.'));
console.log(chalk.red(`Error: ${response.statusText}`));
return;
}
const minerData = await response.json();
spinner.succeed(chalk.green('Miner data fetched successfully!'));
// Display miner details
const minerInfo = [
chalk.bold('Miner Details\n'),
`ID ${chalk.cyan(minerData.id)}`,
`Name ${chalk.white(minerData.name)}`,
`Location ${chalk.yellow(minerData.location)}`,
`Description ${chalk.white(minerData.description)}`,
`Created At ${chalk.white(
new Date(minerData.created_at).toLocaleString()
)}`,
`Updated At ${chalk.white(
new Date(minerData.updated_at).toLocaleString()
)}`,
];
console.log('\n');
console.log(
boxen(minerInfo.join('\n'), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'white',
title: '📄 Miner Information',
titleAlignment: 'center',
})
);
// Prepare compute resources table
const resourceTable = new Table({
head: [
chalk.cyan.bold('ID'),
chalk.cyan.bold('Type'),
chalk.cyan.bold('Location'),
chalk.cyan.bold('Price/Hr'),
chalk.cyan.bold('RAM'),
chalk.cyan.bold('Storage'),
chalk.cyan.bold('Specs'),
],
style: { head: [], border: [] },
wordWrap: true,
colWidths: [15, 7, 10, 10, 10, 15, 40],
});
minerData.compute_resources.forEach((resource) => {
const storageInfo = `${resource.storage.type} ${resource.storage.capacity}\nRead: ${resource.storage.read_speed}\nWrite: ${resource.storage.write_speed}`;
let specs = '';
if (
resource.resource_type.toUpperCase() === 'CPU' &&
resource.cpu_specs
) {
specs = `CPU Name: ${resource.cpu_specs.cpu_name}\nTotal CPUs: ${resource.cpu_specs.total_cpus}\nThreads/Core: ${resource.cpu_specs.threads_per_core}\nMax MHz: ${resource.cpu_specs.cpu_max_mhz}`;
} else if (
resource.resource_type.toUpperCase() === 'GPU' &&
resource.gpu_specs
) {
specs = `GPU Name: ${resource.gpu_specs.gpu_name}\nMemory: ${resource.gpu_specs.memory_size}\nCUDA Cores: ${resource.gpu_specs.cuda_cores}\nClock Speed: ${resource.gpu_specs.clock_speed}`;
}
resourceTable.push([
chalk.green(resource.id),
resource.resource_type.toUpperCase(),
chalk.yellow(resource.location),
chalk.magenta(`$${resource.hourly_price}`),
chalk.blue(resource.ram),
chalk.cyan(storageInfo),
chalk.white(specs),
]);
});
console.log(
'\n' +
boxen(
chalk.bold('💻 Compute Resources') +
'\n\n' +
resourceTable.toString(),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
title: '🌟 Resources Overview',
titleAlignment: 'center',
}
)
);
} catch (error) {
spinner.fail(chalk.red('An error occurred while fetching miner data.'));
console.log(chalk.red(`Error: ${error.message}`));
console.log(
chalk.yellow('\nTip: Ensure the API server is running and accessible.')
);
}
}
async function showNavigationMenu() {
const { navChoice } = await inquirer.prompt([
{
type: 'list',
name: 'navChoice',
message: chalk.cyan('What would you like to do next?'),
choices: [
{ name: gradient.passion('↩️ Back to Main Menu'), value: 'MAIN_MENU' },
{ name: gradient.cristal('🚪 Exit'), value: 'EXIT' },
],
prefix: '🌟',
},
]);
return navChoice;
}
// Display sample compute resource format
function displayResourceFormat() {
const table = new Table({
head: [
chalk.cyan.bold('ID'),
chalk.cyan.bold('Type'),
chalk.cyan.bold('Location'),
chalk.cyan.bold('Price/Hr'),
chalk.cyan.bold('RAM'),
chalk.cyan.bold('Storage'),
chalk.cyan.bold('Specs'),
],
style: { head: [], border: ['gray'] },
});
table.push(
[
chalk.green('plrs_01'),
'CPU',
chalk.yellow('NYC'),
chalk.magenta('$2.50'),
chalk.blue('64GB'),
chalk.cyan('2TB NVME\n7000MB/s Read'),
chalk.yellow('32x Intel Xeon\n2 Threads/Core'),
],
[
chalk.green('plrs_02'),
'GPU',
chalk.yellow('LAX'),
chalk.magenta('$3.50'),
chalk.blue('128GB'),
chalk.cyan('5TB NVME\n7000MB/s Read'),
chalk.yellow('NVIDIA A100\n40GB VRAM'),
]
);
console.log(
'\n' +
boxen(
chalk.bold('📊 Resource Format') + '\n\n' + table.toString(),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'cyan',
title: '🌟 Required Format',
titleAlignment: 'center',
}
)
);
}
// Main menu loop
async function mainLoop() {
while (true) {
displayTitle();
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: chalk.cyan('🚀 Select Operation:'),
choices: [
{ name: gradient.passion('▶️ Run Polaris Subnet'), value: 'Run' },
{ name: gradient.mind('💻 Add Compute Resources'), value: 'Add' },
{ name: gradient.cristal('⛏️ Register Miner'), value: 'Register' },
{ name: gradient.summer('👀 View Miner Compute'), value: 'View' },
{ name: gradient.cristal('🚪 Exit'), value: 'EXIT' },
],
prefix: '🌟',
},
]);
if (action === 'EXIT') {
console.log(
boxen(
chalk.yellow.bold('👋 Thanks for using Polaris!\n') +
chalk.gray('See you next time!'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
}
)
);
process.exit(0);
}
switch (action) {
case 'Run':
await runSubnet();
const runNav = await showNavigationMenu();
if (runNav === 'EXIT') {
process.exit(0);
}
console.clear();
break;
case 'Add':
console.log('\n' + chalk.cyan.bold('💻 Add Compute Resources') + '\n');
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: chalk.cyan('What would you like to do?'),
choices: [
{ name: 'Add to existing miner', value: 'existing' },
{ name: 'Process new resources', value: 'new' }
]
}
]);
if (choice === 'existing') {
await addComputeToMiner();
} else {
displayResourceFormat();
const { filePath } = await inquirer.prompt([
{
type: 'input',
name: 'filePath',
message: chalk.cyan('\n📂 Drag and drop your compute resources Excel file here (or enter path): '),
validate: (input) => {
const cleanPath = input.trim().replace(/^["']|["']$/g, '');
try {
const normalizedPath = path.normalize(cleanPath);
return fs.existsSync(normalizedPath) && normalizedPath.endsWith('.xlsx')
? true
: chalk.red('Please provide a valid Excel file (.xlsx)');
} catch (error) {
return chalk.red('Invalid file path');
}
},
}
]);
const cleanPath = filePath.trim().replace(/^["']|["']$/g, '');
const normalizedPath = path.normalize(cleanPath);
await processComputeResources(normalizedPath);
}
const addNav = await showNavigationMenu();
if (addNav === 'EXIT') {
process.exit(0);
}
console.clear();
break;
case 'Register':
await registerMiner();
const regNav = await showNavigationMenu();
if (regNav === 'EXIT') {
process.exit(0);
}
console.clear();
break;
case 'View':
await viewMinerCompute();
const viewNav = await showNavigationMenu();
if (viewNav === 'EXIT') {
process.exit(0);
}
console.clear();
break;
}
}
}
// Main command
program
.command('start')
.description('Start Polaris with options')
.action(async () => {
await mainLoop();
});
// Parse command-line arguments
program.parse(process.argv);