Simplified the CLI to directly accept a Rhai script path as its primary argument, remove all commands

This commit is contained in:
Sameh Abouelsaad 2025-05-08 14:02:58 +03:00
parent 0890db4810
commit a86a247180
7 changed files with 18 additions and 1334 deletions

View File

@ -10,7 +10,7 @@ license = "MIT"
crate-type = ["cdylib", "rlib"]
[[bin]]
name = "crypto-cli"
name = "hero-vault"
path = "src/main.rs"
[dependencies]

View File

@ -1,772 +0,0 @@
use colored::Colorize;
use std::fs;
use std::io::{self, Read, Write};
use std::path::Path;
use crate::cli::error::{CliError, Result};
use crate::cli::{CryptoCommands, EthereumCommands, KeyCommands};
use webassembly::core::keypair;
use webassembly::core::symmetric;
use webassembly::core::ethereum;
use webassembly::core::error::CryptoError;
// Load a key space from disk
fn load_key_space(name: &str, password: &str) -> Result<()> {
// Load config to get key spaces directory
let config = crate::cli::config::Config::default();
// Check if directory exists
if !config.key_spaces_dir.exists() {
return Err(CliError::ConfigError("Key spaces directory does not exist".to_string()));
}
// Get the key space file path
let space_path = config.key_spaces_dir.join(format!("{}.json", name));
// Check if file exists
if !space_path.exists() {
return Err(CliError::ConfigError(format!("Key space file not found: {}", space_path.display())));
}
// Read the file
let serialized = fs::read_to_string(&space_path)?;
// Deserialize the encrypted space
let encrypted_space = match symmetric::deserialize_encrypted_space(&serialized) {
Ok(space) => space,
Err(e) => {
println!("Error deserializing key space: {}", e);
return Err(CliError::CryptoError(format!("Failed to deserialize key space: {}", e)));
}
};
// Decrypt the space
let space = match symmetric::decrypt_key_space(&encrypted_space, password) {
Ok(space) => space,
Err(e) => {
println!("Error decrypting key space: {}", e);
return Err(CliError::CryptoError(format!("Failed to decrypt key space: {}", e)));
}
};
// Set as current space
keypair::set_current_space(space)?;
Ok(())
}
// Execute key management commands
pub fn execute_key_command(command: &KeyCommands) -> Result<()> {
match command {
KeyCommands::Load { name, password } => {
println!("Loading key space: {}", name);
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", name);
rpassword::read_password()?
}
};
// Load the key space
load_key_space(name, &pwd)?;
println!("{}", "Key space loaded successfully".green());
Ok(())
},
KeyCommands::CreateSpace { name, password } => {
println!("Creating key space: {}", name);
// Create the key space
keypair::create_space(name)?;
// Get the current space
let space = keypair::get_current_space()?;
// Encrypt the key space with the provided password
let encrypted_space = symmetric::encrypt_key_space(&space, &password)?;
// Load config to get key spaces directory
let config = crate::cli::config::Config::default();
config.ensure_key_spaces_dir()?;
// Store the encrypted space to disk
let space_path = config.key_spaces_dir.join(format!("{}.json", name));
let serialized = symmetric::serialize_encrypted_space(&encrypted_space)?;
fs::write(&space_path, serialized)?;
println!("Key space encrypted with password and saved to {}", space_path.display());
println!("{}", "Key space created successfully".green());
Ok(())
},
KeyCommands::ListSpaces => {
println!("Listing key spaces:");
// Load config to get key spaces directory
let config = crate::cli::config::Config::default();
// Check if directory exists
if !config.key_spaces_dir.exists() {
println!("No key spaces found (directory does not exist)");
return Ok(());
}
// List all space files
let mut spaces_found = false;
for entry in fs::read_dir(&config.key_spaces_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "json") {
if let Some(name) = path.file_stem() {
println!("- {}", name.to_string_lossy());
spaces_found = true;
}
}
}
if !spaces_found {
println!("No key spaces found in {}", config.key_spaces_dir.display());
}
// Show the current space if it exists
match keypair::get_current_space() {
Ok(space) => {
println!("\nCurrent active space: {}", space.name.green());
},
Err(_) => {
println!("\nNo active key space");
}
}
Ok(())
},
KeyCommands::CreateKeypair { name, space, password } => {
println!("Creating keypair: {}", name);
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
// If space is provided, try to load it
if let Some(space_name) = space {
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", space_name);
rpassword::read_password()?
}
};
// Load the key space
load_key_space(&space_name, &pwd)?;
println!("Loaded key space: {}", space_name);
} else {
// Try to load the default space
println!("No active key space. Enter the name of the key space to use:");
let mut space_name = String::new();
io::stdin().read_line(&mut space_name)?;
space_name = space_name.trim().to_string();
println!("Enter password for key space {}:", space_name);
let pwd = rpassword::read_password()?;
// Load the key space
load_key_space(&space_name, &pwd)?;
println!("Loaded key space: {}", space_name);
}
}
// Create the keypair in the current space
keypair::create_keypair(name)?;
// Save the key space to disk
let space = keypair::get_current_space()?;
// Get the space name
let space_name = space.name.clone();
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", space_name);
rpassword::read_password()?
}
};
// Encrypt the key space
let encrypted_space = symmetric::encrypt_key_space(&space, &pwd)?;
// Load config to get key spaces directory
let config = crate::cli::config::Config::default();
config.ensure_key_spaces_dir()?;
// Store the encrypted space to disk
let space_path = config.key_spaces_dir.join(format!("{}.json", space_name));
let serialized = symmetric::serialize_encrypted_space(&encrypted_space)?;
fs::write(&space_path, serialized)?;
println!("Key space saved to {}", space_path.display());
println!("{}", "Keypair created successfully".green());
Ok(())
},
KeyCommands::ListKeypairs { space, password } => {
println!("Listing keypairs:");
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
// If space is provided, try to load it
if let Some(space_name) = space {
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", space_name);
rpassword::read_password()?
}
};
// Load the key space
match load_key_space(&space_name, &pwd) {
Ok(_) => println!("Loaded key space: {}", space_name),
Err(e) => {
println!("Error loading key space: {}", e);
return Err(e);
}
}
} else {
// Try to load the default space
println!("No active key space. Enter the name of the key space to use:");
let mut space_name = String::new();
io::stdin().read_line(&mut space_name)?;
space_name = space_name.trim().to_string();
println!("Enter password for key space {}:", space_name);
let pwd = rpassword::read_password()?;
// Load the key space
match load_key_space(&space_name, &pwd) {
Ok(_) => println!("Loaded key space: {}", space_name),
Err(e) => {
println!("Error loading key space: {}", e);
return Err(e);
}
}
}
}
// Get the current space
let space = match keypair::get_current_space() {
Ok(s) => s,
Err(e) => {
println!("Error getting current key space: {}", e);
return Err(CliError::CryptoError(format!("Failed to get current key space: {}", e)));
}
};
// List keypairs directly from the space
let keypairs = space.list_keypairs();
if keypairs.is_empty() {
println!("No keypairs found");
} else {
for (i, name) in keypairs.iter().enumerate() {
println!("{}. {}", i + 1, name);
}
}
Ok(())
},
KeyCommands::Export { name, output, space, password } => {
println!("Exporting keypair: {}", name);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Select the keypair
keypair::select_keypair(name)?;
// Get the keypair
let kp = keypair::get_selected_keypair()?;
// Serialize the keypair
let serialized = serde_json::to_string_pretty(&kp)
.map_err(|e| CliError::CryptoError(format!("Failed to serialize keypair: {}", e)))?;
// Output the serialized keypair
match output {
Some(path) => {
fs::write(path, &serialized)?;
println!("{}", format!("Keypair exported to {}", path).green());
},
None => {
println!("{}", serialized);
}
}
Ok(())
},
KeyCommands::Import { name, input, space: space_name, password } => {
println!("Importing keypair: {}", name);
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
// If space is provided, try to load it
if let Some(space_name) = space_name {
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", space_name);
rpassword::read_password()?
}
};
// Load the key space
load_key_space(&space_name, &pwd)?;
println!("Loaded key space: {}", space_name);
} else {
// Try to load the default space
println!("No active key space. Enter the name of the key space to use:");
let mut space_name = String::new();
io::stdin().read_line(&mut space_name)?;
space_name = space_name.trim().to_string();
println!("Enter password for key space {}:", space_name);
let pwd = rpassword::read_password()?;
// Load the key space
load_key_space(&space_name, &pwd)?;
println!("Loaded key space: {}", space_name);
}
}
// Get input data
let import_data = match input {
Some(path) => fs::read_to_string(path)?,
None => {
println!("Enter keypair data (end with Ctrl+D on Unix or Ctrl+Z on Windows):");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
}
};
// Deserialize the keypair
let kp: keypair::KeyPair = serde_json::from_str(&import_data)
.map_err(|e| CliError::CryptoError(format!("Failed to deserialize keypair: {}", e)))?;
// Get the current space
let mut space = keypair::get_current_space()?;
// Add the keypair to the space
space.keypairs.insert(name.to_string(), kp);
// Update the space
keypair::set_current_space(space)?;
// Auto-save the key space
let space = keypair::get_current_space()?;
let space_name = space.name.clone();
// Get password
let pwd = match password {
Some(p) => p.clone(),
None => {
println!("Enter password for key space {}:", space_name);
rpassword::read_password()?
}
};
// Encrypt the key space
let encrypted_space = symmetric::encrypt_key_space(&space, &pwd)?;
// Load config to get key spaces directory
let config = crate::cli::config::Config::default();
config.ensure_key_spaces_dir()?;
// Store the encrypted space to disk
let space_path = config.key_spaces_dir.join(format!("{}.json", space_name));
let serialized = symmetric::serialize_encrypted_space(&encrypted_space)?;
fs::write(&space_path, serialized)?;
println!("Key space saved to {}", space_path.display());
println!("{}", "Keypair imported successfully".green());
Ok(())
},
}
}
// Execute cryptographic operation commands
pub fn execute_crypto_command(command: &CryptoCommands) -> Result<()> {
match command {
CryptoCommands::Sign { message, input, keypair, output, space, password } => {
println!("Signing with keypair: {}", keypair);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Select the keypair
keypair::select_keypair(keypair)?;
// Get message to sign
let msg = match (message, input) {
(Some(m), _) => m.clone(),
(_, Some(path)) => fs::read_to_string(path)?,
_ => {
println!("Enter message to sign (end with Ctrl+D on Unix or Ctrl+Z on Windows):");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
}
};
// Sign the message
let signature_bytes = keypair::keypair_sign(msg.as_bytes())?;
// Encode the signature as base64
let signature = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &signature_bytes);
// Output signature
match output {
Some(path) => {
fs::write(path, &signature)?;
println!("{}", format!("Signature written to {}", path).green());
},
None => {
println!("Signature: {}", signature);
}
}
Ok(())
},
CryptoCommands::Verify { message, input, signature, keypair, pubkey, space, password } => {
println!("Verifying signature");
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space and a keypair is specified
if let Some(kp) = keypair {
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
}
// Get message to verify
let msg = match (message, input) {
(Some(m), _) => m.clone(),
(_, Some(path)) => fs::read_to_string(path)?,
_ => {
println!("Enter message to verify (end with Ctrl+D on Unix or Ctrl+Z on Windows):");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
}
};
// Decode the signature from base64
let signature_bytes = match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, signature) {
Ok(bytes) => bytes,
Err(e) => {
return Err(CliError::CryptoError(format!("Invalid signature format: {}", e)));
}
};
// Verify the signature
let is_valid = if let Some(kp) = keypair {
// Select the keypair and verify
keypair::select_keypair(kp)?;
keypair::keypair_verify(msg.as_bytes(), &signature_bytes)?
} else if let Some(pk) = pubkey {
// Decode the public key from base64
let pubkey_bytes = match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, pk) {
Ok(bytes) => bytes,
Err(e) => {
return Err(CliError::CryptoError(format!("Invalid public key format: {}", e)));
}
};
// Verify with the public key
keypair::verify_with_public_key(&pubkey_bytes, msg.as_bytes(), &signature_bytes)?
} else {
// Use the currently selected keypair
keypair::keypair_verify(msg.as_bytes(), &signature_bytes)?
};
if is_valid {
println!("{}", "Signature is valid".green());
} else {
println!("{}", "Signature is invalid".red());
}
Ok(())
},
CryptoCommands::Encrypt { data, input, recipient, output, space, password } => {
println!("Encrypting for recipient: {}", recipient);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Get data to encrypt
let plaintext = match (data, input) {
(Some(d), _) => d.clone(),
(_, Some(path)) => fs::read_to_string(path)?,
_ => {
println!("Enter data to encrypt (end with Ctrl+D on Unix or Ctrl+Z on Windows):");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
}
};
// Get the recipient's public key
// For now, we'll assume the recipient is a keypair name
keypair::select_keypair(&recipient)?;
let recipient_pubkey = keypair::keypair_pub_key()?;
// Encrypt the data
let ciphertext_bytes = keypair::encrypt_asymmetric(&recipient_pubkey, plaintext.as_bytes())?;
// Encode the ciphertext as base64
let ciphertext = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &ciphertext_bytes);
// Output ciphertext
match output {
Some(path) => {
fs::write(path, &ciphertext)?;
println!("{}", format!("Encrypted data written to {}", path).green());
},
None => {
println!("Encrypted data: {}", ciphertext);
}
}
Ok(())
},
CryptoCommands::Decrypt { data, input, keypair, output, space, password } => {
println!("Decrypting with keypair: {}", keypair);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Select the keypair
keypair::select_keypair(keypair)?;
// Get data to decrypt
let ciphertext = match (data, input) {
(Some(d), _) => d.clone(),
(_, Some(path)) => fs::read_to_string(path)?,
_ => {
println!("Enter data to decrypt (end with Ctrl+D on Unix or Ctrl+Z on Windows):");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
}
};
// Decode the ciphertext from base64
let ciphertext_bytes = match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &ciphertext) {
Ok(bytes) => bytes,
Err(e) => {
return Err(CliError::CryptoError(format!("Invalid ciphertext format: {}", e)));
}
};
// Decrypt the data
let plaintext_bytes = keypair::decrypt_asymmetric(&ciphertext_bytes)?;
// Convert the plaintext to a string
let plaintext = match String::from_utf8(plaintext_bytes) {
Ok(text) => text,
Err(e) => {
return Err(CliError::CryptoError(format!("Invalid UTF-8 in decrypted data: {}", e)));
}
};
// Output plaintext
match output {
Some(path) => {
fs::write(path, &plaintext)?;
println!("{}", format!("Decrypted data written to {}", path).green());
},
None => {
println!("Decrypted data: {}", plaintext);
}
}
Ok(())
},
}
}
// Execute Ethereum wallet commands
pub fn execute_ethereum_command(command: &EthereumCommands) -> Result<()> {
match command {
EthereumCommands::Create { keypair, space, password } => {
println!("Creating Ethereum wallet from keypair: {}", keypair);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Select the keypair
keypair::select_keypair(keypair)?;
// Create the Ethereum wallet
let wallet = ethereum::create_ethereum_wallet()?;
println!("{}", "Ethereum wallet created successfully".green());
println!("Address: {}", wallet.address_string());
Ok(())
},
EthereumCommands::Address { keypair, space, password } => {
println!("Getting Ethereum address for keypair: {}", keypair);
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
// Select the keypair
keypair::select_keypair(keypair)?;
// Get the Ethereum address
match ethereum::get_current_ethereum_wallet() {
Ok(wallet) => {
println!("Ethereum address: {}", wallet.address_string());
Ok(())
},
Err(e) => {
// If no wallet exists, create one
if let CryptoError::NoKeypairSelected = e {
println!("No Ethereum wallet found for this keypair. Creating one...");
let wallet = ethereum::create_ethereum_wallet()?;
println!("Ethereum address: {}", wallet.address_string());
Ok(())
} else {
Err(e.into())
}
}
}
},
EthereumCommands::Balance { address, network, space, password } => {
// If space and password are provided, load the key space
if let (Some(s), Some(p)) = (space, password) {
load_key_space(s, p)?;
println!("Loaded key space: {}", s);
}
// Check if we have an active space
if let Err(_) = keypair::get_current_space() {
return Err(CliError::CryptoError("No active key space. Please use 'key load' command first or provide --space and --password".to_string()));
}
let addr = match address {
Some(a) => a.clone(),
None => {
// Try to get the address from the current wallet
match ethereum::get_current_ethereum_wallet() {
Ok(wallet) => wallet.address_string(),
Err(_) => {
println!("Enter Ethereum address:");
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
buffer.trim().to_string()
}
}
}
};
println!("Getting balance for address {} on network {}", addr, network);
// Create provider based on network
let provider = match network.to_lowercase().as_str() {
"gnosis" => ethereum::create_gnosis_provider()?,
_ => {
return Err(CliError::CryptoError(format!("Unsupported network: {}", network)));
}
};
// Parse the address
let eth_address = addr.parse::<ethers::types::Address>()
.map_err(|_| CliError::CryptoError("Invalid Ethereum address".to_string()))?;
// Get the balance
let rt = tokio::runtime::Runtime::new()
.map_err(|e| CliError::IoError(format!("Failed to create runtime: {}", e)))?;
let balance = rt.block_on(ethereum::get_balance(&provider, eth_address))?;
// Format the balance
let formatted_balance = ethereum::format_eth_balance(balance);
println!("Balance: {}", formatted_balance);
Ok(())
},
}
}
// Helper function to read file contents
fn read_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let mut file = fs::File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
// Helper function to write file contents
fn write_file<P: AsRef<Path>>(path: P, data: &[u8]) -> io::Result<()> {
let mut file = fs::File::create(path)?;
file.write_all(data)?;
Ok(())
}

View File

@ -14,7 +14,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".crypto-cli").join("key-spaces");
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
Config {
default_key_space: None,
@ -30,7 +30,7 @@ impl Config {
Some(p) => PathBuf::from(p.as_ref()),
None => {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
home_dir.join(".crypto-cli").join("config.json")
home_dir.join(".hero-vault").join("config.json")
}
};
@ -50,7 +50,7 @@ impl Config {
Some(p) => PathBuf::from(p.as_ref()),
None => {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let config_dir = home_dir.join(".crypto-cli");
let config_dir = home_dir.join(".hero-vault");
if !config_dir.exists() {
fs::create_dir_all(&config_dir)

View File

@ -1,16 +1,14 @@
pub mod commands;
pub mod config;
pub mod error;
pub mod shell;
use clap::{Parser, Subcommand};
use clap::Parser;
#[derive(Parser)]
#[command(name = "crypto-cli")]
#[command(name = "hero-vault")]
#[command(about = "Cryptographic operations CLI with Rhai scripting support", long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// Path to Rhai script file to execute
pub script_path: String,
#[arg(short, long, help = "Enable verbose output")]
pub verbose: bool,
@ -18,237 +16,3 @@ pub struct Cli {
#[arg(short, long, help = "Config file path")]
pub config: Option<String>,
}
#[derive(Subcommand)]
pub enum Commands {
/// Key management commands
Key {
#[command(subcommand)]
command: KeyCommands,
},
/// Encryption/decryption commands
Crypto {
#[command(subcommand)]
command: CryptoCommands,
},
/// Ethereum wallet commands
Ethereum {
#[command(subcommand)]
command: EthereumCommands,
},
/// Execute Rhai script
Script {
#[arg(help = "Path to Rhai script file")]
path: Option<String>,
#[arg(short, long, help = "Execute script from string")]
inline: Option<String>,
},
/// Interactive shell
Shell,
}
// Define subcommands for each category
#[derive(Subcommand)]
pub enum KeyCommands {
/// Create a new key space
CreateSpace {
/// Name of the key space
name: String,
#[arg(long)]
/// Password to encrypt the key space (required)
password: String
},
/// List available key spaces
ListSpaces,
/// Load a key space
Load {
/// Name of the key space to load
name: String,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Create a new keypair in the current key space
CreateKeypair {
/// Name of the keypair
name: String,
#[arg(long)]
space: Option<String>,
#[arg(long)]
password: Option<String>
},
/// List keypairs in the current key space
ListKeypairs {
#[arg(long)]
/// Key space to list keypairs from
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Export a keypair
Export {
/// Name of the keypair to export
name: String,
/// Output file path (prints to stdout if not specified)
output: Option<String>,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Import a keypair
Import {
/// Name to give the imported keypair
name: String,
/// Input file path (reads from stdin if not specified)
input: Option<String>,
#[arg(long)]
/// Key space to import the keypair into
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
}
#[derive(Subcommand)]
pub enum CryptoCommands {
/// Sign a message with a keypair
Sign {
#[arg(long)]
/// Message to sign (as a string)
message: Option<String>,
#[arg(long)]
/// Input file containing the message to sign
input: Option<String>,
#[arg(long)]
/// Name of the keypair to use for signing
keypair: String,
#[arg(long)]
/// Output file for the signature (prints to stdout if not specified)
output: Option<String>,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Verify a signature
Verify {
#[arg(long)]
/// Message to verify (as a string)
message: Option<String>,
#[arg(long)]
/// Input file containing the message to verify
input: Option<String>,
#[arg(long)]
/// Signature to verify (base64 encoded)
signature: String,
#[arg(long)]
/// Name of the keypair to use for verification
keypair: Option<String>,
#[arg(long)]
/// Public key to use for verification (base64 encoded)
pubkey: Option<String>,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Encrypt data for a recipient
Encrypt {
#[arg(long)]
/// Data to encrypt (as a string)
data: Option<String>,
#[arg(long)]
/// Input file containing the data to encrypt
input: Option<String>,
#[arg(long)]
/// Name of the recipient keypair
recipient: String,
#[arg(long)]
/// Output file for the encrypted data (prints to stdout if not specified)
output: Option<String>,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Decrypt data with a keypair
Decrypt {
#[arg(long)]
/// Data to decrypt (as a string, base64 encoded)
data: Option<String>,
#[arg(long)]
/// Input file containing the data to decrypt
input: Option<String>,
#[arg(long)]
/// Name of the keypair to use for decryption
keypair: String,
#[arg(long)]
/// Output file for the decrypted data (prints to stdout if not specified)
output: Option<String>,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
}
#[derive(Subcommand)]
pub enum EthereumCommands {
/// Create an Ethereum wallet from a keypair
Create {
#[arg(long)]
/// Name of the keypair to use
keypair: String,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Get the Ethereum address for a keypair
Address {
#[arg(long)]
/// Name of the keypair
keypair: String,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
/// Get the balance of an Ethereum address
Balance {
#[arg(long)]
/// Ethereum address (uses the current wallet if not specified)
address: Option<String>,
#[arg(long)]
/// Network to use (e.g., "gnosis")
network: String,
#[arg(long)]
/// Key space containing the keypair
space: Option<String>,
#[arg(long)]
/// Password to decrypt the key space
password: Option<String>
},
}

View File

@ -1,284 +0,0 @@
use colored::Colorize;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
use std::process;
use crate::cli::commands::{execute_crypto_command, execute_ethereum_command, execute_key_command};
use crate::cli::error::{CliError, Result};
use crate::cli::{CryptoCommands, EthereumCommands, KeyCommands};
pub fn run_interactive_shell() -> Result<()> {
println!("{}", "Crypto CLI Interactive Shell".green().bold());
println!("Type 'help' for a list of commands, 'exit' to quit");
let mut rl = DefaultEditor::new().map_err(|e| CliError::IoError(e.to_string()))?;
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}
loop {
let readline = rl.readline("crypto> ");
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str());
let line = line.trim();
if line.is_empty() {
continue;
}
match line {
"exit" | "quit" => {
println!("Goodbye!");
break;
},
"help" => {
print_help();
},
_ => {
if let Err(e) = process_command(line) {
println!("{}: {}", "Error".red().bold(), e);
}
}
}
},
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
},
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
},
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
}
rl.save_history("history.txt").map_err(|e| CliError::IoError(e.to_string()))?;
Ok(())
}
fn print_help() {
println!("{}", "Available Commands:".green().bold());
println!(" {}", "Key Management:".yellow());
println!(" key create-space <name> [password]");
println!(" key list-spaces");
println!(" key load <name> [password]");
println!(" key create-keypair <name>");
println!(" key list-keypairs");
println!(" key export <name> [output-file]");
println!(" key import <name> [input-file]");
println!(" {}", "Cryptographic Operations:".yellow());
println!(" crypto sign <keypair> <message> [output-file]");
println!(" crypto verify <signature> <message> [keypair]");
println!(" crypto encrypt <recipient> <data> [output-file]");
println!(" crypto decrypt <keypair> <data> [output-file]");
println!(" {}", "Ethereum Operations:".yellow());
println!(" eth create <keypair>");
println!(" eth address <keypair>");
println!(" eth balance <address> <network>");
println!(" {}", "General:".yellow());
println!(" help - Show this help message");
println!(" exit - Exit the shell");
}
fn process_command(cmd: &str) -> Result<()> {
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.is_empty() {
return Ok(());
}
match parts[0] {
"key" => {
if parts.len() < 2 {
println!("Missing key subcommand. Try 'help' for a list of commands.");
return Ok(());
}
match parts[1] {
"create-space" => {
if parts.len() < 3 {
println!("Missing space name. Usage: key create-space <name> [password]");
return Ok(());
}
let name = parts[2].to_string();
let password = if parts.len() > 3 { parts[3].to_string() } else { String::new() };
execute_key_command(&KeyCommands::CreateSpace { name, password })
},
"list-spaces" => {
execute_key_command(&KeyCommands::ListSpaces)
},
"load" => {
if parts.len() < 3 {
println!("Missing space name. Usage: key load <name> [password]");
return Ok(());
}
let name = parts[2].to_string();
let password = if parts.len() > 3 { Some(parts[3].to_string()) } else { None };
execute_key_command(&KeyCommands::Load { name, password })
},
"create-keypair" => {
if parts.len() < 3 {
println!("Missing keypair name. Usage: key create-keypair <name>");
return Ok(());
}
let name = parts[2].to_string();
execute_key_command(&KeyCommands::CreateKeypair { name, space: None, password: None })
},
"list-keypairs" => {
execute_key_command(&KeyCommands::ListKeypairs { space: None, password: None })
},
"export" => {
if parts.len() < 3 {
println!("Missing keypair name. Usage: key export <name> [output-file]");
return Ok(());
}
let name = parts[2].to_string();
let output = if parts.len() > 3 { Some(parts[3].to_string()) } else { None };
execute_key_command(&KeyCommands::Export { name, output, space: None, password: None })
},
"import" => {
if parts.len() < 3 {
println!("Missing keypair name. Usage: key import <name> [input-file]");
return Ok(());
}
let name = parts[2].to_string();
let input = if parts.len() > 3 { Some(parts[3].to_string()) } else { None };
execute_key_command(&KeyCommands::Import { name, input, space: None, password: None })
},
_ => {
println!("Unknown key subcommand: {}. Try 'help' for a list of commands.", parts[1]);
Ok(())
}
}
},
"crypto" => {
if parts.len() < 2 {
println!("Missing crypto subcommand. Try 'help' for a list of commands.");
return Ok(());
}
match parts[1] {
"sign" => {
if parts.len() < 4 {
println!("Missing arguments. Usage: crypto sign <keypair> <message> [output-file]");
return Ok(());
}
let keypair = parts[2].to_string();
let message = Some(parts[3].to_string());
let output = if parts.len() > 4 { Some(parts[4].to_string()) } else { None };
execute_crypto_command(&CryptoCommands::Sign { message, input: None, keypair, output, space: None, password: None })
},
"verify" => {
if parts.len() < 4 {
println!("Missing arguments. Usage: crypto verify <signature> <message> [keypair]");
return Ok(());
}
let signature = parts[2].to_string();
let message = Some(parts[3].to_string());
let keypair = if parts.len() > 4 { Some(parts[4].to_string()) } else { None };
execute_crypto_command(&CryptoCommands::Verify { message, input: None, signature, keypair, pubkey: None, space: None, password: None })
},
"encrypt" => {
if parts.len() < 4 {
println!("Missing arguments. Usage: crypto encrypt <recipient> <data> [output-file]");
return Ok(());
}
let recipient = parts[2].to_string();
let data = Some(parts[3].to_string());
let output = if parts.len() > 4 { Some(parts[4].to_string()) } else { None };
execute_crypto_command(&CryptoCommands::Encrypt { data, input: None, recipient, output, space: None, password: None })
},
"decrypt" => {
if parts.len() < 4 {
println!("Missing arguments. Usage: crypto decrypt <keypair> <data> [output-file]");
return Ok(());
}
let keypair = parts[2].to_string();
let data = Some(parts[3].to_string());
let output = if parts.len() > 4 { Some(parts[4].to_string()) } else { None };
execute_crypto_command(&CryptoCommands::Decrypt { data, input: None, keypair, output, space: None, password: None })
},
_ => {
println!("Unknown crypto subcommand: {}. Try 'help' for a list of commands.", parts[1]);
Ok(())
}
}
},
"eth" => {
if parts.len() < 2 {
println!("Missing eth subcommand. Try 'help' for a list of commands.");
return Ok(());
}
match parts[1] {
"create" => {
if parts.len() < 3 {
println!("Missing keypair name. Usage: eth create <keypair>");
return Ok(());
}
let keypair = parts[2].to_string();
execute_ethereum_command(&EthereumCommands::Create { keypair, space: None, password: None })
},
"address" => {
if parts.len() < 3 {
println!("Missing keypair name. Usage: eth address <keypair>");
return Ok(());
}
let keypair = parts[2].to_string();
execute_ethereum_command(&EthereumCommands::Address { keypair, space: None, password: None })
},
"balance" => {
if parts.len() < 4 {
println!("Missing arguments. Usage: eth balance <address> <network>");
return Ok(());
}
let address = Some(parts[2].to_string());
let network = parts[3].to_string();
execute_ethereum_command(&EthereumCommands::Balance { address, network, space: None, password: None })
},
_ => {
println!("Unknown eth subcommand: {}. Try 'help' for a list of commands.", parts[1]);
Ok(())
}
}
},
_ => {
println!("Unknown command: {}. Try 'help' for a list of commands.", parts[0]);
Ok(())
}
}
}

View File

@ -1,5 +1,4 @@
use clap::Parser;
use colored::Colorize;
use env_logger::Builder;
use log::{info, LevelFilter};
@ -9,7 +8,7 @@ extern crate webassembly;
mod cli;
mod scripting;
use cli::{Cli, Commands};
use cli::Cli;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command line arguments
@ -24,35 +23,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
builder.init();
// Execute the appropriate command
match &cli.command {
Commands::Key { command } => {
cli::commands::execute_key_command(command)?;
},
Commands::Crypto { command } => {
cli::commands::execute_crypto_command(command)?;
},
Commands::Ethereum { command } => {
cli::commands::execute_ethereum_command(command)?;
},
Commands::Script { path, inline } => {
// Initialize script engine
let mut engine = scripting::ScriptEngine::new();
if let Some(script_path) = path {
info!("Executing script from file: {}", script_path);
engine.eval_file(script_path)?;
} else if let Some(script) = inline {
info!("Executing inline script");
engine.eval(script)?;
} else {
println!("Error: No script provided");
return Ok(());
}
},
Commands::Shell => {
cli::shell::run_interactive_shell()?;
},
}
// Execute the script
info!("Executing script from file: {}", cli.script_path);
engine.eval_file(&cli.script_path)?;
Ok(())
}

View File

@ -13,7 +13,7 @@ use std::path::PathBuf;
fn load_key_space(name: &str, password: &str) -> bool {
// Get the key spaces directory from config
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".crypto-cli").join("key-spaces");
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Check if directory exists
if !key_spaces_dir.exists() {
@ -93,7 +93,7 @@ fn create_key_space(name: &str, password: &str) -> bool {
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".crypto-cli").join("key-spaces");
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {
@ -156,7 +156,7 @@ fn auto_save_key_space(password: &str) -> bool {
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".crypto-cli").join("key-spaces");
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {