//! Development Mode Server //! //! Provides HTTP API for web-based debugging. //! Only compiled when the `dev-server` feature is enabled. //! //! Security: //! - Only binds to localhost (127.0.0.1) //! - CORS restricted to localhost:1420 (Vite dev server) //! - Not included in release builds #![cfg(feature = "dev-server")] use axum::{ http::{header, Method}, response::{IntoResponse, Json}, routing::{get, post}, Router, }; use std::net::SocketAddr; use tower_http::cors::CorsLayer; use tracing::{error, info, warn}; pub const DEV_SERVER_PORT: u16 = 50051; #[derive(Clone, Default)] pub struct DevServerState {} pub async fn start_dev_server() -> Result<(DevServerState, tokio::task::JoinHandle<()>), String> { let state = DevServerState::default(); let state_clone = state.clone(); let handle = tokio::spawn(async move { run_server(state_clone).await; }); Ok((state, handle)) } async fn run_server(state: DevServerState) { let app = Router::new() .route("/health", get(health_check)) .route("/api/kernel/status", get(kernel_status)) .route("/api/agents", get(agents_list)) .route("/api/skills", get(skills_list)) .route("/api/hands", get(hands_list)) .route("/api/pipelines", get(pipelines_list)) .route("/api/rpc", post(json_rpc_handler)) .layer( CorsLayer::new() .allow_origin([ "http://localhost:1420".parse().unwrap(), "http://127.0.0.1:1420".parse().unwrap(), "http://localhost:5173".parse().unwrap(), "http://127.0.0.1:5173".parse().unwrap(), ]) .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION]), ) .with_state(state); let addr = SocketAddr::from(([127, 0, 0, 1], DEV_SERVER_PORT)); info!("[DevServer] Starting development server on http://{}", addr); let listener = match tokio::net::TcpListener::bind(addr).await { Ok(l) => l, Err(e) => { error!("[DevServer] Failed to bind to {}: {}", addr, e); warn!("[DevServer] Port {} may be in use. Web debugging will not be available.", DEV_SERVER_PORT); return; } }; if let Err(e) = axum::serve(listener, app).await { error!("[DevServer] Server error: {}", e); } } async fn health_check() -> impl IntoResponse { Json(serde_json::json!({ "status": "ok", "mode": "development", "version": env!("CARGO_PKG_VERSION"), "message": "ZCLAW Development Server - Use Tauri app for full functionality" })) } async fn kernel_status() -> impl IntoResponse { Json(serde_json::json!({ "initialized": false, "mode": "development", "message": "Use Tauri app for kernel operations" })) } async fn agents_list() -> impl IntoResponse { Json(serde_json::json!({"agents": []})) } async fn skills_list() -> impl IntoResponse { Json(serde_json::json!({"skills": []})) } async fn hands_list() -> impl IntoResponse { Json(serde_json::json!({"hands": []})) } async fn pipelines_list() -> impl IntoResponse { Json(Vec::::new()) } async fn json_rpc_handler( Json(request): Json, ) -> impl IntoResponse { let method = request.get("method").and_then(|m| m.as_str()).unwrap_or("unknown"); info!("[DevServer] RPC request: {}", method); Json(serde_json::json!({ "jsonrpc": "2.0", "result": { "mode": "development", "method": method, "message": "Use Tauri app for full functionality" }, "id": request.get("id") })) }