#![deny(clippy::unwrap_used)]
use clap::{Args, Parser, Subcommand};
use forevervm::{
commands::{
auth::{login, logout, signup, whoami},
machine::{machine_list, machine_new},
repl::machine_repl,
},
DEFAULT_SERVER_URL,
};
use forevervm_sdk::api::id_types::MachineName;
use std::collections::HashMap;
use std::time::Duration;
use url::Url;
fn parse_key_val(s: &str) -> Result<(String, String), String> {
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Args)]
pub struct ReplConfig {
machine_name: Option<MachineName>,
#[arg(long, default_value = "15")]
instruction_timeout_seconds: u64,
}
#[derive(Subcommand)]
enum Commands {
Signup {
#[arg(long, default_value = DEFAULT_SERVER_URL)]
api_base_url: Url,
},
Login {
#[arg(long, default_value = DEFAULT_SERVER_URL)]
api_base_url: Url,
},
Logout,
Whoami,
Machine {
#[command(subcommand)]
command: MachineCommands,
},
Repl(ReplConfig),
}
#[derive(Subcommand)]
enum MachineCommands {
New {
#[arg(long = "tag", value_parser = parse_key_val, action = clap::ArgAction::Append)]
tags: Option<Vec<(String, String)>>,
},
List {
#[arg(long = "tag", value_parser = parse_key_val, action = clap::ArgAction::Append)]
tags: Option<Vec<(String, String)>>,
},
Repl(ReplConfig),
}
async fn main_inner() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Signup { api_base_url } => {
signup(api_base_url).await?;
}
Commands::Login { api_base_url } => {
login(api_base_url).await?;
}
Commands::Logout => {
logout().await?;
}
Commands::Whoami => {
whoami().await?;
}
Commands::Machine { command } => match command {
MachineCommands::New { tags } => {
let tags_map = tags
.map(|tags| tags.into_iter().collect::<HashMap<String, String>>())
.unwrap_or_default();
machine_new(tags_map).await?;
}
MachineCommands::List { tags } => {
let tags_map = tags
.map(|tags| tags.into_iter().collect::<HashMap<String, String>>())
.unwrap_or_default();
machine_list(tags_map).await?;
}
MachineCommands::Repl(config) => {
run_repl(config).await?;
}
},
Commands::Repl(config) => {
run_repl(config).await?;
}
}
Ok(())
}
pub async fn run_repl(config: ReplConfig) -> anyhow::Result<()> {
let instruction_timeout = Duration::from_secs(config.instruction_timeout_seconds);
machine_repl(config.machine_name, instruction_timeout).await?;
Ok(())
}
#[tokio::main]
async fn main() {
if let Err(e) = main_inner().await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}