use mockforge_tunnel::{TunnelConfig, TunnelManager, TunnelProvider};
use tracing::{info, warn};
#[derive(clap::Subcommand)]
pub enum TunnelSubcommand {
Start {
#[arg(long, default_value = "http://localhost:3000")]
local_url: String,
#[arg(long, default_value = "self")]
provider: String,
#[arg(long)]
server_url: Option<String>,
#[arg(long)]
auth_token: Option<String>,
#[arg(long)]
subdomain: Option<String>,
#[arg(long)]
custom_domain: Option<String>,
#[arg(long, default_value = "http")]
protocol: String,
#[arg(long)]
region: Option<String>,
#[arg(long, default_value = "true")]
websocket_enabled: bool,
},
Stop {
#[arg(long)]
server_url: Option<String>,
#[arg(long)]
auth_token: Option<String>,
#[arg(long)]
tunnel_id: Option<String>,
},
Status {
#[arg(long)]
server_url: Option<String>,
#[arg(long)]
auth_token: Option<String>,
#[arg(long)]
tunnel_id: Option<String>,
},
List {
#[arg(long)]
server_url: Option<String>,
#[arg(long)]
auth_token: Option<String>,
},
}
pub async fn handle_tunnel_command(cmd: TunnelSubcommand) -> anyhow::Result<()> {
match cmd {
TunnelSubcommand::Start {
local_url,
provider,
server_url,
auth_token,
subdomain,
custom_domain,
protocol,
region,
websocket_enabled,
} => {
let provider_enum = match provider.as_str() {
"self" => TunnelProvider::SelfHosted,
"cloud" => TunnelProvider::Cloud,
"cloudflare" => TunnelProvider::Cloudflare,
_ => {
return Err(anyhow::anyhow!(
"Unknown provider: {}. Supported: self, cloud, cloudflare",
provider
));
}
};
let mut config = TunnelConfig::new(&local_url).with_provider(provider_enum);
if let Some(token) = auth_token {
config.auth_token = Some(token);
}
if let Some(subdomain) = subdomain {
config.subdomain = Some(subdomain);
}
if let Some(custom_domain) = custom_domain {
config.custom_domain = Some(custom_domain);
}
if let Some(region) = region {
config.region = Some(region);
}
config.protocol = protocol;
config.websocket_enabled = websocket_enabled;
if matches!(config.provider, TunnelProvider::SelfHosted) {
config.server_url = Some(
server_url
.or_else(|| std::env::var("MOCKFORGE_TUNNEL_SERVER_URL").ok())
.ok_or_else(|| {
anyhow::anyhow!("server_url required for self-hosted provider. Set via --server-url or MOCKFORGE_TUNNEL_SERVER_URL env var")
})?
);
}
let manager = TunnelManager::new(&config)?;
info!("Creating tunnel to {}...", local_url);
let status = manager.create_tunnel(&config).await?;
println!("✅ Tunnel created successfully!");
println!(" Public URL: {}", status.public_url);
println!(" Tunnel ID: {}", status.tunnel_id);
println!(" Status: {}", if status.active { "Active" } else { "Inactive" });
if let Some(expires_at) = status.expires_at {
println!(" Expires at: {}", expires_at);
}
Ok(())
}
TunnelSubcommand::Stop {
server_url,
auth_token,
tunnel_id,
} => {
let mut config = TunnelConfig::default();
config.server_url =
server_url.or_else(|| std::env::var("MOCKFORGE_TUNNEL_SERVER_URL").ok());
if config.server_url.is_none() {
return Err(anyhow::anyhow!("server_url required. Set via --server-url or MOCKFORGE_TUNNEL_SERVER_URL env var"));
}
if let Some(token) = auth_token {
config.auth_token = Some(token);
}
let manager = TunnelManager::new(&config)?;
if let Some(tunnel_id) = tunnel_id {
warn!("Stopping specific tunnel by ID not yet implemented. Use status to get tunnel ID first.");
Ok(())
} else {
info!("Stopping active tunnel...");
manager.stop_tunnel().await?;
println!("✅ Tunnel stopped successfully");
Ok(())
}
}
TunnelSubcommand::Status {
server_url,
auth_token,
tunnel_id,
} => {
let mut config = TunnelConfig::default();
config.server_url =
server_url.or_else(|| std::env::var("MOCKFORGE_TUNNEL_SERVER_URL").ok());
if config.server_url.is_none() {
return Err(anyhow::anyhow!("server_url required. Set via --server-url or MOCKFORGE_TUNNEL_SERVER_URL env var"));
}
if let Some(token) = auth_token {
config.auth_token = Some(token);
}
let manager = TunnelManager::new(&config)?;
if let Some(tunnel_id) = tunnel_id {
let status = manager
.list_tunnels()
.await?
.into_iter()
.find(|t| t.tunnel_id == tunnel_id)
.ok_or_else(|| anyhow::anyhow!("Tunnel not found: {}", tunnel_id))?;
print_tunnel_status(&status);
} else {
if let Some(status) = manager.get_status().await? {
print_tunnel_status(&status);
} else {
println!("No active tunnel found");
}
}
Ok(())
}
TunnelSubcommand::List {
server_url,
auth_token,
} => {
let mut config = TunnelConfig::default();
config.server_url =
server_url.or_else(|| std::env::var("MOCKFORGE_TUNNEL_SERVER_URL").ok());
if config.server_url.is_none() {
return Err(anyhow::anyhow!("server_url required. Set via --server-url or MOCKFORGE_TUNNEL_SERVER_URL env var"));
}
if let Some(token) = auth_token {
config.auth_token = Some(token);
}
let manager = TunnelManager::new(&config)?;
let tunnels = manager.list_tunnels().await?;
if tunnels.is_empty() {
println!("No active tunnels found");
} else {
println!("Active tunnels ({})", tunnels.len());
println!();
for tunnel in tunnels {
print_tunnel_status(&tunnel);
println!();
}
}
Ok(())
}
}
}
fn print_tunnel_status(status: &mockforge_tunnel::TunnelStatus) {
println!("Tunnel ID: {}", status.tunnel_id);
println!(" Public URL: {}", status.public_url);
println!(" Status: {}", if status.active { "Active" } else { "Inactive" });
println!(" Requests: {}", status.request_count);
println!(" Bytes: {}", status.bytes_transferred);
if let Some(created_at) = status.created_at {
println!(" Created: {}", created_at);
}
if let Some(expires_at) = status.expires_at {
println!(" Expires: {}", expires_at);
}
}