diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index 502302503..b979efbf5 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -12,7 +12,6 @@ path = "src/main.rs" [features] default = [] -server = ["dep:fabro-server"] sleep_inhibitor = ["dep:core-foundation"] [lints] @@ -36,7 +35,7 @@ fabro-checkpoint = { path = "../fabro-checkpoint" } fabro-graphviz = { path = "../fabro-graphviz" } fabro-validate = { path = "../fabro-validate" } fabro-workflow = { path = "../fabro-workflow" } -fabro-server = { path = "../fabro-server", optional = true } +fabro-server = { path = "../fabro-server" } fabro-telemetry = { path = "../fabro-telemetry" } fabro-store = { path = "../fabro-store" } fabro-types = { path = "../fabro-types" } diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs index 4336cf698..4822311b2 100644 --- a/lib/crates/fabro-cli/src/args.rs +++ b/lib/crates/fabro-cli/src/args.rs @@ -41,7 +41,6 @@ pub(crate) struct GlobalArgs { #[arg(long, global = true, env = "FABRO_STORAGE_DIR")] pub storage_dir: Option, - #[cfg(feature = "server")] /// Server URL (overrides server.base_url from user.toml) #[arg( long, @@ -767,7 +766,6 @@ pub(crate) enum Commands { command: Option, }, /// Server operations - #[cfg(feature = "server")] Server(ServerNamespace), /// Check environment and integration health Doctor { @@ -855,7 +853,6 @@ impl Commands { Some(ModelsCommand::Test { .. }) => "model test", None => "model", }, - #[cfg(feature = "server")] Self::Server(ns) => match &ns.command { ServerCommand::Start { .. } => "server start", ServerCommand::Stop { .. } => "server stop", @@ -972,17 +969,14 @@ pub(crate) enum SecretCommand { Set(SecretSetArgs), } -#[cfg(feature = "server")] #[derive(Args)] pub(crate) struct ServerNamespace { #[command(subcommand)] pub(crate) command: ServerCommand, } -#[cfg(feature = "server")] use fabro_server::serve::ServeArgs; -#[cfg(feature = "server")] #[derive(Subcommand)] pub(crate) enum ServerCommand { /// Start the HTTP API server diff --git a/lib/crates/fabro-cli/src/commands/doctor.rs b/lib/crates/fabro-cli/src/commands/doctor.rs index 12fe83adb..37c284826 100644 --- a/lib/crates/fabro-cli/src/commands/doctor.rs +++ b/lib/crates/fabro-cli/src/commands/doctor.rs @@ -1,12 +1,9 @@ use std::path::PathBuf; -#[cfg(feature = "server")] use std::process::Command; -#[cfg(feature = "server")] use std::sync::LazyLock; use base64::Engine as _; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; -#[cfg(feature = "server")] use fabro_config::server::{ApiAuthStrategy, AuthProvider}; use fabro_config::user::{default_user_config_path, legacy_user_config_path}; use fabro_llm::client::Client as LlmClient; @@ -17,9 +14,7 @@ pub(crate) use fabro_util::check_report::{ }; use fabro_util::terminal::Styles; use futures::future::join_all; -#[cfg(feature = "server")] use regex::Regex; -#[cfg(feature = "server")] use semver::Version; use crate::args::GlobalArgs; @@ -30,7 +25,6 @@ use crate::user_config::load_user_settings; // System dependency types and parsers (server mode only) // --------------------------------------------------------------------------- -#[cfg(feature = "server")] pub struct DepSpec { pub name: &'static str, command: &'static [&'static str], @@ -39,7 +33,6 @@ pub struct DepSpec { pattern: &'static LazyLock, } -#[cfg(feature = "server")] #[derive(Debug, Clone, PartialEq)] pub enum ProbeOutcome { NotFound, @@ -47,16 +40,12 @@ pub enum ProbeOutcome { Ok { version: Option }, } -#[cfg(feature = "server")] static OPENSSL_RE: LazyLock = LazyLock::new(|| Regex::new(r"(?:OpenSSL|LibreSSL)\s+(\d+)\.(\d+)\.(\d+)").unwrap()); -#[cfg(feature = "server")] static NODE_RE: LazyLock = LazyLock::new(|| Regex::new(r"v(\d+)\.(\d+)\.(\d+)").unwrap()); -#[cfg(feature = "server")] static DOT_RE: LazyLock = LazyLock::new(|| Regex::new(r"graphviz version (\d+)\.(\d+)\.(\d+)").unwrap()); -#[cfg(feature = "server")] fn parse_version(re: &Regex, output: &str) -> Option { let caps = re.captures(output)?; Some(Version::new( @@ -66,7 +55,6 @@ fn parse_version(re: &Regex, output: &str) -> Option { )) } -#[cfg(feature = "server")] pub const DEP_SPECS: &[DepSpec] = &[ DepSpec { name: "openssl", @@ -91,7 +79,6 @@ pub const DEP_SPECS: &[DepSpec] = &[ }, ]; -#[cfg(feature = "server")] pub fn probe_system_deps() -> Vec { DEP_SPECS .iter() @@ -116,7 +103,6 @@ pub fn probe_system_deps() -> Vec { .collect() } -#[cfg(feature = "server")] fn dep_issue(name: &str, issue: &str, required: bool) -> (CheckStatus, String) { let severity = if required { "required" } else { "optional" }; let status = if required { @@ -127,7 +113,6 @@ fn dep_issue(name: &str, issue: &str, required: bool) -> (CheckStatus, String) { (status, format!("{name}: {issue} ({severity})")) } -#[cfg(feature = "server")] pub fn check_system_deps(specs: &[DepSpec], outcomes: &[ProbeOutcome]) -> CheckResult { let mut details = Vec::new(); let mut worst_status = CheckStatus::Pass; @@ -414,11 +399,8 @@ pub(crate) struct GithubAppStatus { /// Result of attempting to sign a JWT with the configured credentials. /// `None` if app_id or private key is missing. pub sign_result: Option>, - #[cfg(feature = "server")] pub client_id: bool, - #[cfg(feature = "server")] pub client_secret: bool, - #[cfg(feature = "server")] pub webhook_secret: bool, } @@ -429,17 +411,9 @@ impl GithubAppStatus { fn none_set(&self) -> bool { let core_none = self.app_id.is_none() && !self.private_key_set; - #[cfg(feature = "server")] - { - core_none && !self.client_id && !self.client_secret && !self.webhook_secret - } - #[cfg(not(feature = "server"))] - { - core_none - } + core_none && !self.client_id && !self.client_secret && !self.webhook_secret } - #[cfg(feature = "server")] fn all_set(&self) -> bool { self.core_set() && self.client_id && self.client_secret && self.webhook_secret } @@ -463,13 +437,11 @@ pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult { } ))); - #[allow(unused_mut)] let mut fields: Vec<(&str, bool)> = vec![ ("git.app_id", status.app_id.is_some()), ("GITHUB_APP_PRIVATE_KEY", status.private_key_set), ]; - #[cfg(feature = "server")] { let server_fields: Vec<(&str, bool)> = vec![ ("git.client_id", status.client_id), @@ -530,7 +502,6 @@ pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult { }; } - #[cfg(feature = "server")] if status.all_set() { return CheckResult { name: "GitHub App".to_string(), @@ -541,17 +512,6 @@ pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult { }; } - #[cfg(not(feature = "server"))] - if status.core_set() { - return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Pass, - summary: "configured".to_string(), - details, - remediation: None, - }; - } - let missing: Vec<_> = fields .iter() .filter(|(_, set)| !set) @@ -566,13 +526,11 @@ pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult { } } -#[cfg(feature = "server")] pub struct ApiStatus { pub base_url: String, pub authentication_strategies: Vec, } -#[cfg(feature = "server")] fn format_auth_strategies(strategies: &[ApiAuthStrategy]) -> String { strategies .iter() @@ -584,7 +542,6 @@ fn format_auth_strategies(strategies: &[ApiAuthStrategy]) -> String { .join(", ") } -#[cfg(feature = "server")] pub fn check_api(status: &ApiStatus, live_result: Option<&Result<(), String>>) -> CheckResult { let mut details = vec![ CheckDetail::new(format!("Base URL: {}", status.base_url)), @@ -609,14 +566,12 @@ pub fn check_api(status: &ApiStatus, live_result: Option<&Result<(), String>>) - } } -#[cfg(feature = "server")] pub struct WebStatus { pub url: String, pub auth_provider: AuthProvider, pub allowed_usernames_count: usize, } -#[cfg(feature = "server")] fn format_auth_provider(provider: &AuthProvider) -> &'static str { match provider { AuthProvider::Github => "github", @@ -624,7 +579,6 @@ fn format_auth_provider(provider: &AuthProvider) -> &'static str { } } -#[cfg(feature = "server")] pub fn check_web(status: &WebStatus, live_result: Option<&Result<(), String>>) -> CheckResult { let mut details = vec![ CheckDetail::new(format!("URL: {}", status.url)), @@ -657,14 +611,12 @@ pub fn check_web(status: &WebStatus, live_result: Option<&Result<(), String>>) - // Cryptographic key validation // --------------------------------------------------------------------------- -#[cfg(feature = "server")] pub struct TlsCheckInput { pub cert_pem: String, pub key_pem: String, pub ca_pem: String, } -#[cfg(feature = "server")] pub struct CryptoInput { pub auth_strategies: Vec, pub tls_files: Option>, @@ -674,7 +626,6 @@ pub struct CryptoInput { pub now_epoch: i64, } -#[cfg(feature = "server")] fn decode_pem_value(name: &str, value: &str) -> Result { if value.starts_with("-----") { return Ok(value.to_string()); @@ -684,7 +635,6 @@ fn decode_pem_value(name: &str, value: &str) -> Result { String::from_utf8(bytes).map_err(|e| format!("{name} base64 decoded to invalid UTF-8: {e}")) } -#[cfg(feature = "server")] fn validate_tls_cert(pem: &str, now_epoch: i64) -> Result { let mut reader = std::io::Cursor::new(pem.as_bytes()); let certs: Vec<_> = rustls_pemfile::certs(&mut reader) @@ -708,7 +658,6 @@ fn validate_tls_cert(pem: &str, now_epoch: i64) -> Result { Ok(format!("CN={cn}, valid")) } -#[cfg(feature = "server")] fn validate_tls_private_key(pem: &str) -> Result<(), String> { let mut reader = std::io::Cursor::new(pem.as_bytes()); rustls_pemfile::private_key(&mut reader) @@ -717,7 +666,6 @@ fn validate_tls_private_key(pem: &str) -> Result<(), String> { Ok(()) } -#[cfg(feature = "server")] fn validate_tls_ca(pem: &str) -> Result<(), String> { let mut reader = std::io::Cursor::new(pem.as_bytes()); let certs: Vec<_> = rustls_pemfile::certs(&mut reader) @@ -729,7 +677,6 @@ fn validate_tls_ca(pem: &str) -> Result<(), String> { Ok(()) } -#[cfg(feature = "server")] fn validate_session_secret(value: &str) -> Result<(), String> { if value.len() < 64 { return Err(format!( @@ -743,14 +690,12 @@ fn validate_session_secret(value: &str) -> Result<(), String> { Ok(()) } -#[cfg(feature = "server")] struct CryptoCheckState { details: Vec, errors: Vec, worst: CheckStatus, } -#[cfg(feature = "server")] impl CryptoCheckState { fn new() -> Self { Self { @@ -787,7 +732,6 @@ impl CryptoCheckState { } } -#[cfg(feature = "server")] pub fn check_crypto(input: &CryptoInput) -> CheckResult { let has_jwt = input.auth_strategies.contains(&ApiAuthStrategy::Jwt); let has_mtls = input.auth_strategies.contains(&ApiAuthStrategy::Mtls); @@ -942,7 +886,6 @@ async fn probe_brave_search(http: &reqwest::Client) -> Result<(), String> { } } -#[cfg(feature = "server")] async fn probe_url(http: &reqwest::Client, url: &str) -> Result<(), String> { http.get(url) .send() @@ -988,10 +931,8 @@ pub(crate) async fn run_doctor( let daytona_configured = std::env::var("DAYTONA_API_KEY").is_ok(); - #[cfg(feature = "server")] let server_settings = fabro_config::server::load_server_settings(None).unwrap_or_default(); - #[cfg(feature = "server")] let api_status = { let api = server_settings.api.clone().unwrap_or_default(); ApiStatus { @@ -1000,7 +941,6 @@ pub(crate) async fn run_doctor( } }; - #[cfg(feature = "server")] let web_status = { let web = server_settings.web.clone().unwrap_or_default(); WebStatus { @@ -1010,13 +950,10 @@ pub(crate) async fn run_doctor( } }; - #[cfg(feature = "server")] let server_git = server_settings.git.clone().unwrap_or_default(); - #[cfg(feature = "server")] let server_api = server_settings.api.clone().unwrap_or_default(); - #[cfg(feature = "server")] let server_web = server_settings.web.clone().unwrap_or_default(); let git_app_id = cli_settings.app_id().map(str::to_owned); @@ -1050,15 +987,11 @@ pub(crate) async fn run_doctor( slug: cli_settings.slug().map(str::to_owned), private_key_set: private_key_raw.is_some(), sign_result, - #[cfg(feature = "server")] client_id: server_git.client_id.is_some(), - #[cfg(feature = "server")] client_secret: std::env::var("GITHUB_APP_CLIENT_SECRET").is_ok(), - #[cfg(feature = "server")] webhook_secret: std::env::var("GITHUB_APP_WEBHOOK_SECRET").is_ok(), }; - #[cfg(feature = "server")] let crypto_input = { let has_mtls = server_api .authentication_strategies @@ -1089,16 +1022,13 @@ pub(crate) async fn run_doctor( } }; - #[cfg(feature = "server")] let dep_results = probe_system_deps(); // Live probes (only when --live is set) let sandbox_status; let llm_live_results: Option)>>; let brave_live_result: Option>; - #[cfg(feature = "server")] let api_live_result: Option>; - #[cfg(feature = "server")] let web_live_result: Option>; if live { @@ -1134,30 +1064,18 @@ pub(crate) async fn run_doctor( }; let brave_fut = probe_brave_search(&http); - #[cfg(feature = "server")] - { - let api_url = format!("{}/runs", server_api.base_url); - let api_fut = probe_url(&http, &api_url); - let web_fut = probe_url(&http, &server_web.url); + let api_url = format!("{}/runs", server_api.base_url); + let api_fut = probe_url(&http, &api_url); + let web_fut = probe_url(&http, &server_web.url); - let (sandbox, llm, brave, api, web) = - tokio::join!(sandbox_fut, llm_fut, brave_fut, api_fut, web_fut); + let (sandbox, llm, brave, api, web) = + tokio::join!(sandbox_fut, llm_fut, brave_fut, api_fut, web_fut); - sandbox_status = sandbox; - llm_live_results = llm; - brave_live_result = Some(brave); - api_live_result = Some(api); - web_live_result = Some(web); - } - - #[cfg(not(feature = "server"))] - { - let (sandbox, llm, brave) = tokio::join!(sandbox_fut, llm_fut, brave_fut); - - sandbox_status = sandbox; - llm_live_results = llm; - brave_live_result = Some(brave); - } + sandbox_status = sandbox; + llm_live_results = llm; + brave_live_result = Some(brave); + api_live_result = Some(api); + web_live_result = Some(web); } else { sandbox_status = SandboxStatus { daytona_configured, @@ -1165,15 +1083,11 @@ pub(crate) async fn run_doctor( }; llm_live_results = None; brave_live_result = None; - #[cfg(feature = "server")] - { - api_live_result = None; - web_live_result = None; - } + api_live_result = None; + web_live_result = None; } // Run pure checks - #[allow(unused_mut)] let mut sections = vec![ CheckSection { title: "Required".into(), @@ -1203,7 +1117,6 @@ pub(crate) async fn run_doctor( }, ]; - #[cfg(feature = "server")] sections.push(CheckSection { title: "Server".into(), checks: vec![ @@ -1421,11 +1334,8 @@ mod tests { sign_result: Some(Err( "Signing failed: signature error: UnexpectedError".to_string() )), - #[cfg(feature = "server")] client_id: true, - #[cfg(feature = "server")] client_secret: true, - #[cfg(feature = "server")] webhook_secret: true, }; let result = check_github_app(&status); @@ -1446,11 +1356,8 @@ mod tests { slug: None, private_key_set: false, sign_result: None, - #[cfg(feature = "server")] client_id: false, - #[cfg(feature = "server")] client_secret: false, - #[cfg(feature = "server")] webhook_secret: false, }; let result = check_github_app(&status); @@ -1459,7 +1366,6 @@ mod tests { // -- Server-only checks (check_api, check_web, check_crypto) -- - #[cfg(feature = "server")] mod server_tests { use super::*; @@ -1652,7 +1558,6 @@ mod tests { // -- parse_version (server only) -- #[test] - #[cfg(feature = "server")] fn parse_version_openssl() { assert_eq!( parse_version( @@ -1664,7 +1569,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn parse_version_libressl() { assert_eq!( parse_version(&OPENSSL_RE, "LibreSSL 3.3.6"), @@ -1673,7 +1577,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn parse_version_node() { assert_eq!( parse_version(&NODE_RE, "v22.14.0"), @@ -1682,7 +1585,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn parse_version_dot() { assert_eq!( parse_version(&DOT_RE, "dot - graphviz version 12.2.1 (20241206.2024)"), @@ -1691,7 +1593,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn parse_version_garbage_returns_none() { assert_eq!(parse_version(&OPENSSL_RE, "not a version"), None); assert_eq!(parse_version(&NODE_RE, "node not found"), None); @@ -1700,10 +1601,8 @@ mod tests { // -- check_system_deps (server only) -- - #[cfg(feature = "server")] static TEST_RE: LazyLock = LazyLock::new(|| Regex::new(r"unused").unwrap()); - #[cfg(feature = "server")] fn spec(name: &'static str, required: bool, min_version: Version) -> DepSpec { DepSpec { name, @@ -1715,7 +1614,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_all_present() { let specs = [ spec("openssl", true, Version::new(3, 0, 0)), @@ -1743,7 +1641,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_required_missing_is_error() { let specs = [spec("openssl", true, Version::new(3, 0, 0))]; let outcomes = [ProbeOutcome::NotFound]; @@ -1753,7 +1650,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_optional_missing_is_warning() { let specs = [spec("gh", false, Version::new(2, 0, 0))]; let outcomes = [ProbeOutcome::NotFound]; @@ -1763,7 +1659,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_outdated_is_warning() { let specs = [spec("openssl", true, Version::new(3, 0, 0))]; let outcomes = [ProbeOutcome::Ok { @@ -1776,7 +1671,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_unparseable_success_is_pass() { let specs = [spec("openssl", true, Version::new(3, 0, 0))]; let outcomes = [ProbeOutcome::Ok { version: None }]; @@ -1786,7 +1680,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_required_command_failed_is_error() { let specs = [spec("node", true, Version::new(20, 0, 0))]; let outcomes = [ProbeOutcome::Failed]; @@ -1796,7 +1689,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_optional_command_failed_is_warning() { let specs = [spec("gh", false, Version::new(2, 0, 0))]; let outcomes = [ProbeOutcome::Failed]; @@ -1806,7 +1698,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn check_system_deps_error_beats_warning() { let specs = [ spec("openssl", true, Version::new(3, 0, 0)), @@ -1819,7 +1710,6 @@ mod tests { // -- check_crypto -- - #[cfg(feature = "server")] mod server_crypto_tests { use super::*; diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index d7433b61d..f25c16b9d 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -1,7 +1,5 @@ use anyhow::Result; -#[cfg(feature = "server")] -use fabro_agent::cli::run_with_args_and_client; -use fabro_agent::cli::{AgentArgs, OutputFormat, run_with_args}; +use fabro_agent::cli::{AgentArgs, OutputFormat, run_with_args, run_with_args_and_client}; use fabro_config::mcp::McpServerEntry; use fabro_mcp::config::McpServerSettings; @@ -22,7 +20,6 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result if globals.json { args.output_format = Some(OutputFormat::Json); } - #[cfg(feature = "server")] let resolved = user_config::resolve_mode( globals.storage_dir.as_deref(), globals.server_url.as_deref(), @@ -33,40 +30,31 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result .into_iter() .map(|(name, entry): (String, McpServerEntry)| entry.into_config(name)) .collect(); - #[cfg(feature = "server")] - { - match resolved.mode { - user_config::ExecutionMode::Server => { - tracing::info!(mode = "server", "Agent session starting"); - let http_client = user_config::build_server_client(resolved.tls.as_ref())?; - let provider_name = args - .provider - .clone() - .unwrap_or_else(|| "anthropic".to_string()); - let adapter = std::sync::Arc::new(fabro_llm::providers::FabroServerAdapter::new( - http_client, - &resolved.server_base_url, - &provider_name, - )); - let mut client = - fabro_llm::client::Client::new(std::collections::HashMap::new(), None, vec![]); - client - .register_provider(adapter) - .await - .map_err(|e| anyhow::anyhow!("Failed to register fabro server adapter: {e}"))?; - run_with_args_and_client(args, Some(client), mcp_servers).await? - } - user_config::ExecutionMode::Standalone => { - tracing::info!(mode = "standalone", "Agent session starting"); - run_with_args(args, mcp_servers).await? - } + match resolved.mode { + user_config::ExecutionMode::Server => { + tracing::info!(mode = "server", "Agent session starting"); + let http_client = user_config::build_server_client(resolved.tls.as_ref())?; + let provider_name = args + .provider + .clone() + .unwrap_or_else(|| "anthropic".to_string()); + let adapter = std::sync::Arc::new(fabro_llm::providers::FabroServerAdapter::new( + http_client, + &resolved.server_base_url, + &provider_name, + )); + let mut client = + fabro_llm::client::Client::new(std::collections::HashMap::new(), None, vec![]); + client + .register_provider(adapter) + .await + .map_err(|e| anyhow::anyhow!("Failed to register fabro server adapter: {e}"))?; + run_with_args_and_client(args, Some(client), mcp_servers).await? + } + user_config::ExecutionMode::Standalone => { + tracing::info!(mode = "standalone", "Agent session starting"); + run_with_args(args, mcp_servers).await? } - } - #[cfg(not(feature = "server"))] - { - let _ = globals; - tracing::info!(mode = "standalone", "Agent session starting"); - run_with_args(args, mcp_servers).await?; } Ok(()) diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index 8d264cbf1..0d894dacb 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "server")] use std::io::Write as _; use std::net::SocketAddr; use std::path::Path; @@ -29,11 +28,10 @@ use crate::shared::provider_auth::{ }; // --------------------------------------------------------------------------- -// OpenSSL helpers (server mode only) +// OpenSSL helpers // --------------------------------------------------------------------------- /// Run an openssl subcommand and return stdout on success. -#[cfg(feature = "server")] fn run_openssl(args: &[&str], description: &str) -> Result> { let output = Command::new("openssl") .args(args) @@ -49,7 +47,6 @@ fn run_openssl(args: &[&str], description: &str) -> Result> { } /// Run an openssl subcommand that reads key material from stdin. -#[cfg(feature = "server")] fn run_openssl_with_stdin(args: &[&str], stdin_data: &[u8], description: &str) -> Result> { let mut child = Command::new("openssl") .args(args) @@ -77,10 +74,9 @@ fn run_openssl_with_stdin(args: &[&str], stdin_data: &[u8], description: &str) - } // --------------------------------------------------------------------------- -// Session secret (server mode only) +// Session secret // --------------------------------------------------------------------------- -#[cfg(feature = "server")] fn generate_session_secret() -> String { let mut rng = rand::thread_rng(); let bytes: [u8; 32] = rng.gen(); @@ -88,10 +84,9 @@ fn generate_session_secret() -> String { } // --------------------------------------------------------------------------- -// JWT keypair generation (server mode only) +// JWT keypair generation // --------------------------------------------------------------------------- -#[cfg(feature = "server")] fn generate_jwt_keypair() -> Result<(String, String)> { let private_pem = run_openssl(&["genpkey", "-algorithm", "Ed25519"], "generate keypair")?; let public_pem = @@ -103,10 +98,9 @@ fn generate_jwt_keypair() -> Result<(String, String)> { } // --------------------------------------------------------------------------- -// mTLS certificate generation (server mode only) +// mTLS certificate generation // --------------------------------------------------------------------------- -#[cfg(feature = "server")] fn generate_mtls_certs(dir: &Path) -> Result<()> { std::fs::create_dir_all(dir).context("failed to create certs directory")?; @@ -185,10 +179,9 @@ fn generate_mtls_certs(dir: &Path) -> Result<()> { } // --------------------------------------------------------------------------- -// Config TOML generation (server mode only) +// Config TOML generation // --------------------------------------------------------------------------- -#[cfg(feature = "server")] fn format_config_toml(username: &str) -> String { format!( r#"[web] @@ -229,7 +222,6 @@ fn detect_binary_on_path(binary: &str) -> bool { // Interactive setup // --------------------------------------------------------------------------- -#[cfg(feature = "server")] fn prompt_input(prompt: &str) -> Result { Ok(dialoguer::Input::with_theme(&ColorfulTheme::default()) .with_prompt(prompt) @@ -508,8 +500,7 @@ pub(crate) async fn run_install(web_url: &str, globals: &GlobalArgs) -> Result<( .join(".fabro"); std::fs::create_dir_all(&arc_dir)?; - // Pre-flight checks (server mode only — standalone doesn't need openssl/node/dot) - #[cfg(feature = "server")] + // Pre-flight checks { eprintln!( " {}", @@ -680,8 +671,7 @@ pub(crate) async fn run_install(web_url: &str, globals: &GlobalArgs) -> Result<( } eprintln!(); - // Server configuration (server mode only) - #[cfg(feature = "server")] + // Server configuration { eprintln!(" {}", s.bold.apply_to("Server · Configuration")); eprintln!(" {}", s.dim.apply_to("─────────────────────")); @@ -713,8 +703,7 @@ pub(crate) async fn run_install(web_url: &str, globals: &GlobalArgs) -> Result<( eprintln!(); } - // Secrets and certificates (server mode only) - #[cfg(feature = "server")] + // Secrets and certificates { eprintln!( " {}", @@ -772,10 +761,9 @@ pub(crate) async fn run_install(web_url: &str, globals: &GlobalArgs) -> Result<( } // --------------------------------------------------------------------------- -// Hex encoding (server mode only — used by generate_session_secret) +// Hex encoding (used by generate_session_secret) // --------------------------------------------------------------------------- -#[cfg(feature = "server")] mod hex { pub fn encode(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{b:02x}")).collect() @@ -802,33 +790,29 @@ mod tests { assert!(!detect_binary_on_path("arc_nonexistent_xyz")); } - // -- Session secret (server only) -- + // -- Session secret -- #[test] - #[cfg(feature = "server")] fn session_secret_length() { let secret = generate_session_secret(); assert_eq!(secret.len(), 64); } #[test] - #[cfg(feature = "server")] fn session_secret_is_hex() { let secret = generate_session_secret(); assert!(secret.chars().all(|c| c.is_ascii_hexdigit())); } #[test] - #[cfg(feature = "server")] fn session_secret_is_lowercase() { let secret = generate_session_secret(); assert!(secret.chars().all(|c| !c.is_ascii_uppercase())); } - // -- JWT keypair (server only) -- + // -- JWT keypair -- #[test] - #[cfg(feature = "server")] fn jwt_keypair_private_pem_header() { let (private, _) = generate_jwt_keypair().unwrap(); assert!( @@ -838,7 +822,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn jwt_keypair_public_pem_header() { let (_, public) = generate_jwt_keypair().unwrap(); assert!( @@ -848,16 +831,14 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn jwt_keypair_public_parses() { let (_, public) = generate_jwt_keypair().unwrap(); jsonwebtoken::DecodingKey::from_ed_pem(public.as_bytes()).expect("public key should parse"); } - // -- mTLS cert generation (server only) -- + // -- mTLS cert generation -- #[test] - #[cfg(feature = "server")] fn mtls_certs_creates_files() { let dir = tempfile::tempdir().unwrap(); let certs_dir = dir.path().join("certs"); @@ -870,7 +851,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn mtls_ca_cert_is_pem() { let dir = tempfile::tempdir().unwrap(); let certs_dir = dir.path().join("certs"); @@ -884,7 +864,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn mtls_server_cert_is_pem() { let dir = tempfile::tempdir().unwrap(); let certs_dir = dir.path().join("certs"); @@ -898,7 +877,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn mtls_certs_parse_via_rustls() { let dir = tempfile::tempdir().unwrap(); let certs_dir = dir.path().join("certs"); @@ -919,10 +897,9 @@ mod tests { assert_eq!(server_certs.len(), 1); } - // -- Config TOML generation (server only) -- + // -- Config TOML generation -- #[test] - #[cfg(feature = "server")] fn config_toml_roundtrips() { let toml_str = format_config_toml("brynary"); let settings: fabro_types::Settings = @@ -934,7 +911,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn config_toml_has_auth_strategies() { let toml_str = format_config_toml("alice"); let settings: fabro_types::Settings = toml::from_str(&toml_str).unwrap(); @@ -948,7 +924,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn config_toml_has_tls_paths() { use std::path::PathBuf; let toml_str = format_config_toml("bob"); diff --git a/lib/crates/fabro-cli/src/commands/llm/chat.rs b/lib/crates/fabro-cli/src/commands/llm/chat.rs index af9cbfd83..e7eb8a32d 100644 --- a/lib/crates/fabro-cli/src/commands/llm/chat.rs +++ b/lib/crates/fabro-cli/src/commands/llm/chat.rs @@ -1,7 +1,5 @@ use anyhow::Result; -use fabro_llm::cli::{ChatArgs, run_chat}; -#[cfg(feature = "server")] -use fabro_llm::cli::{ServerConnection, run_chat_via_server}; +use fabro_llm::cli::{ChatArgs, ServerConnection, run_chat, run_chat_via_server}; use fabro_types::Settings; use crate::args::GlobalArgs; @@ -17,32 +15,23 @@ pub(super) async fn execute( args.model = llm_defaults.and_then(|l| l.model.clone()); } - #[cfg(feature = "server")] - { - let resolved = crate::user_config::resolve_mode( - globals.storage_dir.as_deref(), - globals.server_url.as_deref(), - cli_settings, - ); - match resolved.mode { - crate::user_config::ExecutionMode::Server => { - let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; - let server = ServerConnection { - client, - base_url: resolved.server_base_url, - }; - run_chat_via_server(args, &server).await?; - } - crate::user_config::ExecutionMode::Standalone => { - run_chat(args).await?; - } + let resolved = crate::user_config::resolve_mode( + globals.storage_dir.as_deref(), + globals.server_url.as_deref(), + cli_settings, + ); + match resolved.mode { + crate::user_config::ExecutionMode::Server => { + let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; + let server = ServerConnection { + client, + base_url: resolved.server_base_url, + }; + run_chat_via_server(args, &server).await?; + } + crate::user_config::ExecutionMode::Standalone => { + run_chat(args).await?; } - } - - #[cfg(not(feature = "server"))] - { - let _ = globals; - run_chat(args).await?; } Ok(()) diff --git a/lib/crates/fabro-cli/src/commands/llm/prompt.rs b/lib/crates/fabro-cli/src/commands/llm/prompt.rs index 68311759e..bcd491de2 100644 --- a/lib/crates/fabro-cli/src/commands/llm/prompt.rs +++ b/lib/crates/fabro-cli/src/commands/llm/prompt.rs @@ -1,7 +1,5 @@ use anyhow::Result; -use fabro_llm::cli::{PromptArgs, run_prompt}; -#[cfg(feature = "server")] -use fabro_llm::cli::{ServerConnection, run_prompt_via_server}; +use fabro_llm::cli::{PromptArgs, ServerConnection, run_prompt, run_prompt_via_server}; use fabro_types::Settings; use crate::args::GlobalArgs; @@ -16,32 +14,23 @@ pub(super) async fn execute( args.model = llm_defaults.and_then(|l| l.model.clone()); } - #[cfg(feature = "server")] - { - let resolved = crate::user_config::resolve_mode( - globals.storage_dir.as_deref(), - globals.server_url.as_deref(), - cli_settings, - ); - match resolved.mode { - crate::user_config::ExecutionMode::Server => { - let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; - let server = ServerConnection { - client, - base_url: resolved.server_base_url, - }; - run_prompt_via_server(args, &server, globals.json).await?; - } - crate::user_config::ExecutionMode::Standalone => { - run_prompt(args, globals.json).await?; - } + let resolved = crate::user_config::resolve_mode( + globals.storage_dir.as_deref(), + globals.server_url.as_deref(), + cli_settings, + ); + match resolved.mode { + crate::user_config::ExecutionMode::Server => { + let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; + let server = ServerConnection { + client, + base_url: resolved.server_base_url, + }; + run_prompt_via_server(args, &server, globals.json).await?; + } + crate::user_config::ExecutionMode::Standalone => { + run_prompt(args, globals.json).await?; } - } - - #[cfg(not(feature = "server"))] - { - let _ = globals; - run_prompt(args, globals.json).await?; } Ok(()) diff --git a/lib/crates/fabro-cli/src/commands/mod.rs b/lib/crates/fabro-cli/src/commands/mod.rs index e7fb7a942..5a55fe548 100644 --- a/lib/crates/fabro-cli/src/commands/mod.rs +++ b/lib/crates/fabro-cli/src/commands/mod.rs @@ -15,7 +15,6 @@ pub(crate) mod run; pub(crate) mod runs; pub(crate) mod sandbox; pub(crate) mod secret; -#[cfg(feature = "server")] pub(crate) mod server; pub(crate) mod skill; pub(crate) mod store; diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 855bd3fa7..e4a42fbad 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -1,38 +1,25 @@ use anyhow::Result; -#[cfg(feature = "server")] -use fabro_llm::cli::ServerConnection; -use fabro_llm::cli::{ModelsCommand, run_models}; +use fabro_llm::cli::{ModelsCommand, ServerConnection, run_models}; use crate::args::GlobalArgs; -#[cfg(feature = "server")] use crate::user_config; pub(crate) async fn execute(command: Option, globals: &GlobalArgs) -> Result<()> { - let server = { - #[cfg(feature = "server")] - { - let cli_settings = user_config::load_user_settings_with_globals(globals)?; - let resolved = user_config::resolve_mode( - globals.storage_dir.as_deref(), - globals.server_url.as_deref(), - &cli_settings, - ); - match resolved.mode { - user_config::ExecutionMode::Server => { - let client = user_config::build_server_client(resolved.tls.as_ref())?; - Some(ServerConnection { - client, - base_url: resolved.server_base_url, - }) - } - user_config::ExecutionMode::Standalone => None, - } - } - #[cfg(not(feature = "server"))] - { - let _ = globals; - None + let cli_settings = user_config::load_user_settings_with_globals(globals)?; + let resolved = user_config::resolve_mode( + globals.storage_dir.as_deref(), + globals.server_url.as_deref(), + &cli_settings, + ); + let server = match resolved.mode { + user_config::ExecutionMode::Server => { + let client = user_config::build_server_client(resolved.tls.as_ref())?; + Some(ServerConnection { + client, + base_url: resolved.server_base_url, + }) } + user_config::ExecutionMode::Standalone => None, }; run_models(command, server, globals.json).await diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index d58b48f11..ae4b7d608 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -10,9 +10,7 @@ mod store; mod user_config; use anyhow::Result; -use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands}; -#[cfg(feature = "server")] -use args::{ServerCommand, ServerNamespace}; +use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands, ServerCommand, ServerNamespace}; use clap::{CommandFactory, Parser}; use fabro_telemetry::{git, panic as tel_panic, sanitize, sender}; use fabro_util::printer::Printer; @@ -106,37 +104,24 @@ async fn main_inner() -> (String, Result<()>) { let command_name = command.name().to_string(); let (config_log_level, upgrade_check_enabled) = { - #[cfg(feature = "server")] + if let Commands::Server(ServerNamespace { + command: + ServerCommand::Start { + serve_args: args, .. + } + | ServerCommand::Serve { + serve_args: args, .. + }, + }) = command.as_ref() { - if let Commands::Server(ServerNamespace { - command: - ServerCommand::Start { - serve_args: args, .. - } - | ServerCommand::Serve { - serve_args: args, .. - }, - }) = command.as_ref() - { - match fabro_config::server::load_server_settings(args.config.as_deref()) { - Ok(server_settings) => ( - server_settings.log.as_ref().and_then(|l| l.level.clone()), - false, - ), - Err(err) => return (command_name, Err(err)), - } - } else { - match user_config::load_user_settings() { - Ok(cli_settings) => ( - cli_settings.log.as_ref().and_then(|l| l.level.clone()), - cli_settings.upgrade_check_enabled(), - ), - Err(err) => return (command_name, Err(err)), - } + match fabro_config::server::load_server_settings(args.config.as_deref()) { + Ok(server_settings) => ( + server_settings.log.as_ref().and_then(|l| l.level.clone()), + false, + ), + Err(err) => return (command_name, Err(err)), } - } - #[cfg(not(feature = "server"))] - { + } else { match user_config::load_user_settings() { Ok(cli_settings) => ( cli_settings.log.as_ref().and_then(|l| l.level.clone()), @@ -192,7 +177,6 @@ async fn main_inner() -> (String, Result<()>) { Commands::Store(ns) => commands::store::dispatch(ns, &globals).await?, Commands::RunsCmd(cmd) => commands::runs::dispatch(cmd, &globals).await?, Commands::Model { command } => commands::model::execute(command, &globals).await?, - #[cfg(feature = "server")] Commands::Server(ns) => { commands::server::dispatch(ns.command, &globals).await?; } @@ -369,7 +353,6 @@ mod tests { } #[test] - #[cfg(feature = "server")] fn parse_server_url_conflicts_with_storage_dir() { let result = Cli::try_parse_from([ "fabro", diff --git a/lib/crates/fabro-cli/src/user_config.rs b/lib/crates/fabro-cli/src/user_config.rs index 063cbd1f5..f351193eb 100644 --- a/lib/crates/fabro-cli/src/user_config.rs +++ b/lib/crates/fabro-cli/src/user_config.rs @@ -1,17 +1,13 @@ -#[cfg(feature = "server")] use std::path::Path; -#[allow(unused_imports)] pub(crate) use fabro_config::user::*; use fabro_config::ConfigLayer; use fabro_types::Settings; +use tracing::debug; use crate::args::GlobalArgs; -#[cfg(feature = "server")] -use tracing::debug; - pub(crate) fn load_user_settings() -> anyhow::Result { ConfigLayer::user()?.resolve() } @@ -31,7 +27,6 @@ pub(crate) fn apply_global_overrides(mut layer: ConfigLayer, globals: &GlobalArg layer.mode = Some(ExecutionMode::Standalone); } - #[cfg(feature = "server")] if let Some(url) = &globals.server_url { layer.server.get_or_insert_with(Default::default).base_url = Some(url.clone()); layer.mode = Some(ExecutionMode::Server); @@ -40,7 +35,6 @@ pub(crate) fn apply_global_overrides(mut layer: ConfigLayer, globals: &GlobalArg layer } -#[cfg(feature = "server")] #[derive(Debug, PartialEq)] pub(crate) struct ResolvedMode { pub mode: ExecutionMode, @@ -48,10 +42,8 @@ pub(crate) struct ResolvedMode { pub tls: Option, } -#[cfg(feature = "server")] const DEFAULT_SERVER_URL: &str = "http://localhost:3000/api/v1"; -#[cfg(feature = "server")] pub(crate) fn resolve_mode( cli_storage_dir: Option<&Path>, cli_server_url: Option<&str>, @@ -83,7 +75,6 @@ pub(crate) fn resolve_mode( } } -#[cfg(feature = "server")] pub(crate) fn build_server_client( tls: Option<&ClientTlsSettings>, ) -> anyhow::Result { @@ -115,7 +106,7 @@ pub(crate) fn build_server_client( Ok(client) } -#[cfg(all(test, feature = "server"))] +#[cfg(test)] mod tests { use std::path::{Path, PathBuf}; diff --git a/lib/crates/fabro-cli/tests/it/cmd/asset.rs b/lib/crates/fabro-cli/tests/it/cmd/asset.rs index 2adb0236d..62b42a10d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/asset.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/asset.rs @@ -25,6 +25,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/asset_cp.rs b/lib/crates/fabro-cli/tests/it/cmd/asset_cp.rs index 329d29d77..1ae8d09ae 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/asset_cp.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/asset_cp.rs @@ -29,6 +29,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/asset_list.rs b/lib/crates/fabro-cli/tests/it/cmd/asset_list.rs index bc5681295..a9bedf5cd 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/asset_list.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/asset_list.rs @@ -27,6 +27,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs index 5eda6ac9e..2a16a2974 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs @@ -30,6 +30,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); @@ -472,6 +473,13 @@ fn attach_json_errors_without_prompting_for_human_input() { "run_id": "[ULID]", "ts": "[TIMESTAMP]" }, + { + "event": "run.running", + "id": "[EVENT_ID]", + "properties": {}, + "run_id": "[ULID]", + "ts": "[TIMESTAMP]" + }, { "event": "run.started", "id": "[EVENT_ID]", @@ -482,13 +490,6 @@ fn attach_json_errors_without_prompting_for_human_input() { "run_id": "[ULID]", "ts": "[TIMESTAMP]" }, - { - "event": "run.running", - "id": "[EVENT_ID]", - "properties": {}, - "run_id": "[ULID]", - "ts": "[TIMESTAMP]" - }, { "event": "stage.started", "id": "[EVENT_ID]", diff --git a/lib/crates/fabro-cli/tests/it/cmd/completion.rs b/lib/crates/fabro-cli/tests/it/cmd/completion.rs index c3c819aed..6fea74e47 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/completion.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/completion.rs @@ -23,6 +23,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/config.rs b/lib/crates/fabro-cli/tests/it/cmd/config.rs index 111cbb02c..bdea62713 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/config.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/config.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use fabro_config::mcp::McpTransport; -#[cfg(feature = "server")] use fabro_config::user::ExecutionMode; use fabro_test::{fabro_snapshot, test_context}; use fabro_types::Settings; @@ -32,6 +31,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); @@ -570,7 +570,6 @@ shared = "legacy" } #[test] -#[cfg(feature = "server")] fn settings_server_url_overrides_cli_defaults() { let context = test_context!(); let project = setup_settings_fixture(&context); @@ -585,6 +584,7 @@ fn settings_server_url_overrides_cli_defaults() { let output = context .command() + .env_remove("FABRO_STORAGE_DIR") .current_dir(project.path()) .args(["--server-url", "https://cli.example.com", "settings"]) .assert() diff --git a/lib/crates/fabro-cli/tests/it/cmd/create.rs b/lib/crates/fabro-cli/tests/it/cmd/create.rs index 4b0d79c31..5b19fa153 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/create.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/create.rs @@ -35,6 +35,7 @@ fn help() { --model Override default LLM model --provider Override default LLM provider --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -v, --verbose Enable verbose output --sandbox Sandbox for agent tools [possible values: local, docker, daytona] --label Attach a label to this run (repeatable, format: KEY=VALUE) diff --git a/lib/crates/fabro-cli/tests/it/cmd/detached.rs b/lib/crates/fabro-cli/tests/it/cmd/detached.rs index 751fcf403..1abffb362 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/detached.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/detached.rs @@ -27,6 +27,7 @@ fn help() { --resume Resume from checkpoint instead of fresh start --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/diff.rs b/lib/crates/fabro-cli/tests/it/cmd/diff.rs index 56268c5fa..f32a51597 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/diff.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/diff.rs @@ -28,6 +28,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/discord.rs b/lib/crates/fabro-cli/tests/it/cmd/discord.rs index 4bb3195dd..87cbee06d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/discord.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/discord.rs @@ -20,6 +20,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/docs.rs b/lib/crates/fabro-cli/tests/it/cmd/docs.rs index d1af75144..aeecd2b33 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/docs.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/docs.rs @@ -20,6 +20,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/doctor.rs b/lib/crates/fabro-cli/tests/it/cmd/doctor.rs index 0547e67e5..6766cf5f2 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/doctor.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/doctor.rs @@ -30,6 +30,7 @@ fn help() { --no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true] --quiet Suppress non-essential output [env: FABRO_QUIET=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); @@ -60,13 +61,21 @@ fn dry_run_flag() { [!] Cloud sandbox (no sandbox configured) [!] Brave Search (not configured) - Found issues in 4 categories. + Server + [!] System dependencies (some issues) + [✓] Fabro API (http://localhost:3000/api/v1) + [✓] Fabro Web (http://localhost:3000) + [!] Cryptographic keys (no authentication configured) + + Found issues in 6 categories. Warnings: • Configuration — Create ~/.fabro/user.toml • GitHub App — Configure GitHub App in server.toml and set env vars to enable GitHub integration • Cloud sandbox — Set DAYTONA_API_KEY to enable cloud sandbox execution • Brave Search — Set BRAVE_SEARCH_API_KEY to enable web search + • System dependencies — Install missing system dependencies + • Cryptographic keys — Configure authentication_strategies in [api] section of server.toml ----- stderr ----- "); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/exec.rs b/lib/crates/fabro-cli/tests/it/cmd/exec.rs index f0a20f9fb..02b35e80c 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/exec.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/exec.rs @@ -35,6 +35,7 @@ fn help() { --debug Print LLM request/response debug info to stderr --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] --verbose Print full LLM request/response JSON to stderr + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] --skills-dir Directory containing skill files (overrides default discovery) --output-format Output format (text for human-readable, json for NDJSON event stream) [possible values: text, json] -h, --help Print help diff --git a/lib/crates/fabro-cli/tests/it/cmd/fabro.rs b/lib/crates/fabro-cli/tests/it/cmd/fabro.rs index 3077a8bb9..5aa785231 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/fabro.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/fabro.rs @@ -29,6 +29,7 @@ fn help() { rm Remove one or more workflow runs inspect Show detailed information about a workflow run model List and test LLM models + server Server operations doctor Check environment and integration health install Set up the Fabro environment (LLMs, certs, GitHub) pr Pull request operations @@ -52,6 +53,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help -V, --version Print version ----- stderr ----- diff --git a/lib/crates/fabro-cli/tests/it/cmd/fork.rs b/lib/crates/fabro-cli/tests/it/cmd/fork.rs index 989492c6d..488784112 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/fork.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/fork.rs @@ -33,6 +33,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/graph.rs b/lib/crates/fabro-cli/tests/it/cmd/graph.rs index af4fa665c..054e47022 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/graph.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/graph.rs @@ -66,6 +66,11 @@ fn help() { [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url + Server URL (overrides server.base_url from user.toml) + + [env: FABRO_SERVER_URL=] + -h, --help Print help (see a summary with '-h') ----- stderr ----- diff --git a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs index fd1daf2b0..0089de2e7 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs @@ -30,6 +30,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); @@ -68,7 +69,7 @@ fn inspect_completed_run_shows_run_start_conclusion_checkpoint() { let run = setup_completed_dry_run(&context); let output = run_success(&context, &["inspect", &run.run_id]); - assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r###" + assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r#" [ { "run_id": "[ULID]", @@ -86,7 +87,7 @@ fn inspect_completed_run_shows_run_start_conclusion_checkpoint() { "conclusion": { "status": "success", "duration_ms": "[DURATION_MS]", - "stage_count": 3 + "stage_count": null }, "checkpoint": { "current_node": "report", @@ -102,7 +103,7 @@ fn inspect_completed_run_shows_run_start_conclusion_checkpoint() { } } ] - "###); + "#); } #[test] @@ -120,7 +121,7 @@ fn inspect_completed_run_reads_store_without_disk_metadata_files() { } let output = run_success(&context, &["inspect", &run.run_id]); - assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r###" + assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r#" [ { "run_id": "[ULID]", @@ -138,7 +139,7 @@ fn inspect_completed_run_reads_store_without_disk_metadata_files() { "conclusion": { "status": "success", "duration_ms": "[DURATION_MS]", - "stage_count": 3 + "stage_count": null }, "checkpoint": { "current_node": "report", @@ -154,7 +155,7 @@ fn inspect_completed_run_reads_store_without_disk_metadata_files() { } } ] - "###); + "#); } #[test] @@ -165,7 +166,7 @@ fn inspect_git_backed_run_exposes_checkpoint_and_sandbox_state() { assert_snapshot!( serde_json::to_string_pretty(&compact_git_inspect(&output)).unwrap(), - @r###" + @r#" [ { "run_id": "[ULID]", @@ -186,7 +187,7 @@ fn inspect_git_backed_run_exposes_checkpoint_and_sandbox_state() { "status": "success", "duration_ms": "[DURATION_MS]", "final_git_commit_sha": "[SHA]", - "stage_count": 3 + "stage_count": null }, "checkpoint": { "current_node": "step_two", @@ -204,6 +205,6 @@ fn inspect_git_backed_run_exposes_checkpoint_and_sandbox_state() { } } ] - "### + "# ); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/install.rs b/lib/crates/fabro-cli/tests/it/cmd/install.rs index fd0dfe68b..082bac91b 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/install.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/install.rs @@ -21,6 +21,7 @@ fn help() { --quiet Suppress non-essential output [env: FABRO_QUIET=] --verbose Enable verbose output [env: FABRO_VERBOSE=] --storage-dir Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]] + --server-url Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] -h, --help Print help ----- stderr ----- "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/llm_prompt.rs b/lib/crates/fabro-cli/tests/it/cmd/llm_prompt.rs index fc56b01d2..b1f685be0 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/llm_prompt.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/llm_prompt.rs @@ -1,7 +1,5 @@ use fabro_test::{fabro_snapshot, test_context}; -#[cfg(feature = "server")] use httpmock::prelude::*; -#[cfg(feature = "server")] use serde_json::Value; #[test] @@ -33,13 +31,13 @@ fn help() { --verbose Enable verbose output [env: FABRO_VERBOSE=] -o, --option