use crate::commands::{RunCommand, RunTargetArgs};
use crate::context::CliContext;
use crate::services::credentials::CredentialsService;
use anyhow::Result;
use std::process::Command;
#[derive(Debug, Clone, Copy)]
pub enum RunTarget {
Robot,
Remote,
}
impl RunTarget {
fn display_name(&self) -> &'static str {
match self {
RunTarget::Robot => "Robot",
RunTarget::Remote => "Remote",
}
}
fn description(&self) -> &'static str {
match self {
RunTarget::Robot => "edge/on-robot nodes",
RunTarget::Remote => "cloud/remote server nodes",
}
}
}
pub async fn handle_run(ctx: &mut CliContext, command: &RunCommand) -> Result<()> {
match command {
RunCommand::Robot(args) => run_robot_target(ctx, args).await,
RunCommand::Remote(args) => run_remote_target(ctx, args).await,
}
}
async fn run_robot_target(ctx: &mut CliContext, args: &RunTargetArgs) -> Result<()> {
let target = RunTarget::Robot;
println!();
println!("🚀 Running Mecha10 Project - {} Target", target.display_name());
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
if !ctx.is_project_initialized() {
println!("⚠️ Not in a Mecha10 project directory");
println!();
return Err(anyhow::anyhow!("Not in a Mecha10 project"));
}
let project = ctx.project()?;
let project_name = project.name()?;
println!("Project: {}", project_name);
println!("Target: {} ({})", target.display_name(), target.description());
let build_profile = if args.release { "release" } else { "debug" };
println!("Profile: {}", build_profile);
println!();
let redis_url = ctx.redis_url().to_string();
println!("Redis: {}", redis_url);
println!();
let binary_path = ctx.working_dir.join("target").join(build_profile).join(project_name);
if !binary_path.exists() {
println!("❌ Binary not found: {}", binary_path.display());
println!();
println!("Build first with:");
println!(" mecha10 build robot {}", if args.release { "--release" } else { "" });
return Err(anyhow::anyhow!("Binary not found"));
}
println!("Binary: {}", binary_path.display());
println!();
let config = ctx.load_project_config().await?;
let control_plane_url = config.environments.control_plane_url();
let relay_url = config.environments.relay_url();
let robot_id = config.robot.id.clone();
let credentials_service = CredentialsService::new();
let api_key = match credentials_service.get_api_key() {
Ok(Some(key)) => {
println!("Auth: ✓ (logged in)");
key
}
_ => {
println!("Auth: ⚠ Not logged in - run 'mecha10 auth login'");
String::new()
}
};
println!("Control: {}", control_plane_url);
println!("Relay: {}", relay_url);
println!("Robot: {}", robot_id);
println!();
println!("Starting {}...", target.display_name());
println!();
let mut cmd = Command::new(&binary_path);
cmd.arg("run")
.current_dir(&ctx.working_dir)
.env("MECHA10_REDIS_URL", &redis_url)
.env("MECHA10_ENV", "development")
.env("CONTROL_PLANE_URL", &control_plane_url)
.env("WEBRTC_RELAY_URL", &relay_url)
.env("ROBOT_ID", &robot_id)
.env("ROBOT_API_KEY", &api_key);
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
let status = cmd.status()?;
println!();
if status.success() {
println!("✅ {} target stopped gracefully", target.display_name());
} else {
println!(
"❌ {} target exited with error: {:?}",
target.display_name(),
status.code()
);
}
Ok(())
}
async fn run_remote_target(ctx: &mut CliContext, args: &RunTargetArgs) -> Result<()> {
let target = RunTarget::Remote;
println!();
println!("🐳 Running Mecha10 Project - {} Target (Docker)", target.display_name());
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
if !ctx.is_project_initialized() {
println!("⚠️ Not in a Mecha10 project directory");
println!();
return Err(anyhow::anyhow!("Not in a Mecha10 project"));
}
let project = ctx.project()?;
let project_name = project.name()?;
println!("Project: {}", project_name);
println!("Target: {} ({})", target.display_name(), target.description());
println!();
let compose_file = ctx.working_dir.join("docker").join("docker-compose.remote.yml");
if !compose_file.exists() {
println!("❌ Docker compose file not found: {}", compose_file.display());
println!();
println!("Expected: docker/docker-compose.remote.yml");
println!("Run 'mecha10 init' to create a new project with Docker support.");
return Err(anyhow::anyhow!("docker-compose.remote.yml not found"));
}
println!("Compose: {}", compose_file.display());
let config = ctx.load_project_config().await?;
let control_plane_url = config.environments.control_plane_url();
let robot_id = config.robot.id.clone();
let credentials_service = CredentialsService::new();
let api_key = match credentials_service.get_api_key() {
Ok(Some(key)) => {
println!("Auth: ✓ (logged in)");
key
}
_ => {
println!("Auth: ⚠ Not logged in - run 'mecha10 auth login'");
String::new()
}
};
println!("Control: {}", control_plane_url);
println!("Robot: {}", robot_id);
println!();
println!("Starting Docker containers...");
println!();
let mut compose_args = vec!["compose", "-f", "docker/docker-compose.remote.yml", "up", "--build"];
if args.release {
compose_args.push("-d");
println!("Mode: Detached (background)");
} else {
println!("Mode: Attached (foreground, Ctrl+C to stop)");
}
println!();
let github_token = std::env::var("GITHUB_TOKEN").unwrap_or_default();
if !github_token.is_empty() {
println!("GitHub: ✓ (token found)");
} else {
println!("GitHub: ⚠ No GITHUB_TOKEN - will fail if repo is private");
}
let mut cmd = Command::new("docker");
cmd.args(&compose_args)
.current_dir(&ctx.working_dir)
.env("ROBOT_ID", &robot_id)
.env("ROBOT_API_KEY", &api_key)
.env("CONTROL_PLANE_URL", &control_plane_url)
.env("GITHUB_TOKEN", &github_token);
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
let status = cmd.status()?;
println!();
if status.success() {
if args.release {
println!("✅ Remote containers started in background");
println!();
println!("View logs: docker compose -f docker/docker-compose.remote.yml logs -f");
println!("Stop: docker compose -f docker/docker-compose.remote.yml down");
} else {
println!("✅ Remote containers stopped");
}
} else {
println!("❌ Docker compose exited with error: {:?}", status.code());
}
Ok(())
}