diff --git a/lib/crates/fabro-cli/src/command_context.rs b/lib/crates/fabro-cli/src/command_context.rs new file mode 100644 index 000000000..8c33954d0 --- /dev/null +++ b/lib/crates/fabro-cli/src/command_context.rs @@ -0,0 +1,111 @@ +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use anyhow::{Context as _, Result, bail}; +use fabro_types::Settings; +use tokio::sync::OnceCell; + +use crate::args::{ServerConnectionArgs, ServerTargetArgs}; +use crate::server_client::ServerStoreClient; +use crate::{server_client, user_config}; + +#[derive(Clone, Debug)] +pub(crate) enum ServerMode { + None, + ByTarget { + target_override: Option, + }, + ByStorageDir { + target_override: Option, + storage_dir_override: Option, + }, +} + +pub(crate) struct CommandContext { + cwd: PathBuf, + base_config_path: PathBuf, + machine_settings: Settings, + server_mode: ServerMode, + server: OnceCell>, +} + +impl CommandContext { + pub(crate) fn base() -> Result { + Self::new(ServerMode::None) + } + + pub(crate) fn for_target(args: &ServerTargetArgs) -> Result { + Self::new(ServerMode::ByTarget { + target_override: args.server.clone(), + }) + } + + pub(crate) fn for_connection(args: &ServerConnectionArgs) -> Result { + Self::new(ServerMode::ByStorageDir { + target_override: args.target.server.clone(), + storage_dir_override: args.storage_dir.clone_path(), + }) + } + + fn new(server_mode: ServerMode) -> Result { + let cwd = std::env::current_dir().context("Failed to get current directory")?; + let base_config_path = user_config::active_settings_path(None); + let machine_settings = match &server_mode { + ServerMode::None | ServerMode::ByTarget { .. } => user_config::load_settings()?, + ServerMode::ByStorageDir { + storage_dir_override, + .. + } => user_config::load_settings_with_storage_dir(storage_dir_override.as_deref())?, + }; + + Ok(Self { + cwd, + base_config_path, + machine_settings, + server_mode, + server: OnceCell::new(), + }) + } + + pub(crate) fn cwd(&self) -> &Path { + &self.cwd + } + + pub(crate) fn base_config_path(&self) -> &Path { + &self.base_config_path + } + + pub(crate) fn machine_settings(&self) -> &Settings { + &self.machine_settings + } + + pub(crate) async fn server(&self) -> Result> { + let server_mode = self.server_mode.clone(); + let base_config_path = self.base_config_path.clone(); + let machine_settings = self.machine_settings.clone(); + + let client = self + .server + .get_or_try_init(|| async move { + let target = match server_mode { + ServerMode::None => bail!("This command context does not have server access"), + ServerMode::ByTarget { target_override } + | ServerMode::ByStorageDir { + target_override, .. + } => ServerTargetArgs { + server: target_override, + }, + }; + server_client::connect_server_with_settings( + &target, + &machine_settings, + &base_config_path, + ) + .await + .map(Arc::new) + }) + .await?; + + Ok(Arc::clone(client)) + } +} diff --git a/lib/crates/fabro-cli/src/commands/artifact/mod.rs b/lib/crates/fabro-cli/src/commands/artifact/mod.rs index 093fdfda3..0395903f8 100644 --- a/lib/crates/fabro-cli/src/commands/artifact/mod.rs +++ b/lib/crates/fabro-cli/src/commands/artifact/mod.rs @@ -5,6 +5,7 @@ use anyhow::{Context, Result}; use fabro_types::{RunId, StageId}; use crate::args::{ArtifactCommand, ArtifactNamespace, GlobalArgs, ServerTargetArgs}; +use crate::command_context::CommandContext; use crate::server_client::ServerStoreClient; use crate::server_runs::ServerSummaryLookup; @@ -24,7 +25,8 @@ pub(super) async fn resolve_artifacts( node: Option<&str>, retry: Option, ) -> Result<(RunId, ServerStoreClient, Vec)> { - let lookup = ServerSummaryLookup::connect(server).await?; + let ctx = CommandContext::for_target(server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(run_selector)?; let run_id = run.run_id(); let mut entries = Vec::new(); diff --git a/lib/crates/fabro-cli/src/commands/config/mod.rs b/lib/crates/fabro-cli/src/commands/config/mod.rs index 6d7c3c4c0..24698410b 100644 --- a/lib/crates/fabro-cli/src/commands/config/mod.rs +++ b/lib/crates/fabro-cli/src/commands/config/mod.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::path::Path; use crate::args::{GlobalArgs, SettingsArgs}; -use crate::server_client; +use crate::command_context::CommandContext; use crate::shared::print_json_pretty; use crate::user_config; use fabro_config::ConfigLayer; @@ -11,13 +11,19 @@ use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSetting use fabro_config::project; use fabro_types::Settings; -fn config_layers(workflow: Option<&Path>) -> anyhow::Result { - let cwd = std::env::current_dir()?; +fn config_layers( + ctx: &CommandContext, + workflow: Option<&Path>, +) -> anyhow::Result { + let cwd = ctx.cwd(); let (workflow_layer, project_layer) = match workflow { - Some(path) => workflow_and_project_layers(path, &cwd)?, - None => (ConfigLayer::default(), ConfigLayer::project(&cwd)?), + Some(path) => workflow_and_project_layers(path, cwd)?, + None => (ConfigLayer::default(), ConfigLayer::project(cwd)?), }; - let user_layer = user_config::settings_layer_with_storage_dir(None)?; + let user_layer = user_config::settings_layer_with_config_and_storage_dir( + Some(ctx.base_config_path()), + None, + )?; Ok(EffectiveSettingsLayers::new( ConfigLayer::default(), workflow_layer, @@ -52,7 +58,8 @@ fn workflow_and_project_layers( } async fn merged_config(args: &SettingsArgs) -> anyhow::Result { - let layers = config_layers(args.workflow.as_deref())?; + let base_ctx = CommandContext::base()?; + let layers = config_layers(&base_ctx, args.workflow.as_deref())?; if args.local { return effective_settings::resolve_settings( layers, @@ -61,10 +68,9 @@ async fn merged_config(args: &SettingsArgs) -> anyhow::Result { ); } - let machine_settings = user_config::load_settings()?; - let target = user_config::resolve_server_target(&args.target, &machine_settings)?; - let client = server_client::connect_server_only(&args.target).await?; - let server_settings = client.retrieve_server_settings().await?; + let ctx = CommandContext::for_target(&args.target)?; + let target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?; + let server_settings = ctx.server().await?.retrieve_server_settings().await?; let mode = match target { user_config::ServerTarget::HttpUrl { .. } => EffectiveSettingsMode::RemoteServer, user_config::ServerTarget::UnixSocket(_) => EffectiveSettingsMode::LocalDaemon, diff --git a/lib/crates/fabro-cli/src/commands/doctor.rs b/lib/crates/fabro-cli/src/commands/doctor.rs index f7a227f46..ac74284ba 100644 --- a/lib/crates/fabro-cli/src/commands/doctor.rs +++ b/lib/crates/fabro-cli/src/commands/doctor.rs @@ -18,7 +18,7 @@ use regex::Regex; use semver::Version; use crate::args::{DoctorArgs, GlobalArgs}; -use crate::server_client; +use crate::command_context::CommandContext; use crate::shared::print_json_pretty; pub(crate) struct DepSpec { @@ -357,8 +357,38 @@ pub(crate) async fn run_doctor( }], }; - let client = match server_client::connect_server_backed_api_client(&args.target).await { - Ok(client) => client, + let ctx = match CommandContext::for_target(&args.target) { + Ok(ctx) => ctx, + Err(err) => { + report.sections.push(CheckSection { + title: "Server".to_string(), + checks: vec![CheckResult { + name: "Fabro server".to_string(), + status: CheckStatus::Error, + summary: "settings resolution failed".to_string(), + details: vec![CheckDetail::new(err.to_string())], + remediation: Some( + "Fix the local CLI settings or provide `--server`, then run doctor again." + .to_string(), + ), + }], + }); + + if let Some(spinner) = spinner { + spinner.finish_and_clear(); + } + + if globals.json { + print_json_pretty(&report)?; + } else { + render_report(&report, &styles, verbose); + } + return Ok(1); + } + }; + + let server = match ctx.server().await { + Ok(server) => server, Err(err) => { report.sections.push(CheckSection { title: "Server".to_string(), @@ -387,7 +417,7 @@ pub(crate) async fn run_doctor( } }; - let health = match client.get_health().send().await { + let health = match server.api().get_health().send().await { Ok(response) => response.into_inner(), Err(err) => { report.sections.push(CheckSection { @@ -420,7 +450,7 @@ pub(crate) async fn run_doctor( .checks .push(check_version_parity(&health.version)); - match client.run_diagnostics().send().await { + match server.api().run_diagnostics().send().await { Ok(response) => { let diagnostics = response.into_inner(); report diff --git a/lib/crates/fabro-cli/src/commands/graph.rs b/lib/crates/fabro-cli/src/commands/graph.rs index 0ad4f70db..6487f5818 100644 --- a/lib/crates/fabro-cli/src/commands/graph.rs +++ b/lib/crates/fabro-cli/src/commands/graph.rs @@ -7,9 +7,9 @@ use fabro_util::terminal::Styles; use tracing::debug; use crate::args::{GlobalArgs, GraphArgs, GraphDirection, GraphOutputFormat}; +use crate::command_context::CommandContext; use crate::commands::run::output::api_diagnostics_to_local; use crate::manifest_builder::{ManifestBuildInput, build_run_manifest}; -use crate::server_client; use crate::shared::{absolute_or_current, print_diagnostics, print_json_pretty, relative_path}; pub(crate) async fn run( @@ -21,15 +21,15 @@ pub(crate) async fn run( globals.require_no_json()?; } - let cwd = std::env::current_dir()?; + let ctx = CommandContext::for_target(&args.target)?; let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), - cwd, + cwd: ctx.cwd().to_path_buf(), args_layer: ConfigLayer::default(), args: None, run_id: None, })?; - let client = server_client::connect_server_only(&args.target).await?; + let client = ctx.server().await?; let preflight = client.run_preflight(built.manifest.clone()).await?; let diagnostics = api_diagnostics_to_local(&preflight.workflow.diagnostics); diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 08a347abc..6d7739279 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Context, Result, bail}; use cli_table::format::{Border, Justify, Separator}; use cli_table::{Cell, CellStruct, Color, Style, Table}; use fabro_api::{self, types as api_types}; @@ -8,6 +8,7 @@ use serde::Serialize; use serde::de::DeserializeOwned; use crate::args::{GlobalArgs, ModelListArgs, ModelTestArgs, ModelsCommand}; +use crate::command_context::CommandContext; use crate::server_client; #[derive(Serialize)] @@ -42,9 +43,10 @@ pub(crate) async fn execute(command: Option, globals: &GlobalArgs ModelsCommand::List(args) => &args.target, ModelsCommand::Test(args) => &args.target, }; - let client = server_client::connect_server_backed_api_client(target_args).await?; + let ctx = CommandContext::for_target(target_args)?; + let server = ctx.server().await?; - run_models(command, client, globals.json).await + run_models(command, server.api(), globals.json).await } fn format_context_window(tokens: i64) -> String { @@ -171,33 +173,6 @@ fn model_test_row_from_status(model: &Model, status: &str, result_color: Color) } } -fn map_api_error(err: progenitor_client::Error) -> anyhow::Error -where - E: serde::Serialize + std::fmt::Debug, -{ - match err { - progenitor_client::Error::ErrorResponse(response) => { - let status = response.status(); - if let Ok(value) = serde_json::to_value(response.into_inner()) { - if let Some(detail) = value - .get("errors") - .and_then(serde_json::Value::as_array) - .and_then(|errors| errors.first()) - .and_then(|entry| entry.get("detail")) - .and_then(serde_json::Value::as_str) - { - return anyhow!("{detail}"); - } - } - anyhow!("request failed with status {status}") - } - progenitor_client::Error::UnexpectedResponse(response) => { - anyhow!("request failed with status {}", response.status()) - } - other => anyhow!("{other}"), - } -} - fn convert_type(value: TInput) -> Result where TInput: serde::Serialize, @@ -223,7 +198,7 @@ async fn fetch_models_from_server( request = request.query(query.to_string()); } - let response = request.send().await.map_err(map_api_error)?; + let response = request.send().await.map_err(server_client::map_api_error)?; let parsed = response.into_inner(); let count = parsed.data.len() as u64; models.extend(convert_type::<_, Vec>(parsed.data)?); @@ -245,7 +220,7 @@ async fn test_model_via_server( if let Some(mode) = mode { request = request.mode(mode); } - let response = request.send().await.map_err(map_api_error)?; + let response = request.send().await.map_err(server_client::map_api_error)?; Ok(response.into_inner()) } @@ -393,7 +368,7 @@ async fn test_models_via_server( #[allow(clippy::print_stdout)] async fn run_models( command: ModelsCommand, - client: fabro_api::Client, + client: &fabro_api::Client, json_output: bool, ) -> Result<()> { let styles = Styles::detect_stdout(); @@ -403,7 +378,7 @@ async fn run_models( provider, query, .. }) => { let models = - fetch_models_from_server(&client, provider.as_deref(), query.as_deref()).await?; + fetch_models_from_server(client, provider.as_deref(), query.as_deref()).await?; if json_output { println!("{}", serde_json::to_string_pretty(&models)?); @@ -418,7 +393,7 @@ async fn run_models( .. }) => { test_models_via_server( - &client, + client, provider.as_deref(), model.as_deref(), deep, diff --git a/lib/crates/fabro-cli/src/commands/pr/create.rs b/lib/crates/fabro-cli/src/commands/pr/create.rs index 5ae23ebe6..03cbb2db8 100644 --- a/lib/crates/fabro-cli/src/commands/pr/create.rs +++ b/lib/crates/fabro-cli/src/commands/pr/create.rs @@ -6,6 +6,7 @@ use fabro_workflow::pull_request::maybe_open_pull_request; use tracing::info; use crate::args::{GlobalArgs, PrCreateArgs}; +use crate::command_context::CommandContext; use crate::commands::store::rebuild::rebuild_run_store; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; @@ -16,7 +17,8 @@ pub(super) async fn create_command( github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run_id)?; let run_id = run.run_id(); let events = lookup.client().list_run_events(&run_id, None, None).await?; diff --git a/lib/crates/fabro-cli/src/commands/pr/list.rs b/lib/crates/fabro-cli/src/commands/pr/list.rs index 1494d4a54..2f7d15d0f 100644 --- a/lib/crates/fabro-cli/src/commands/pr/list.rs +++ b/lib/crates/fabro-cli/src/commands/pr/list.rs @@ -4,6 +4,7 @@ use serde::Serialize; use tracing::info; use crate::args::{GlobalArgs, PrListArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; @@ -24,7 +25,8 @@ pub(super) async fn list_command( let creds = github_app.context( "GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id", )?; - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let mut entries = Vec::new(); for run in lookup.runs() { diff --git a/lib/crates/fabro-cli/src/commands/pr/mod.rs b/lib/crates/fabro-cli/src/commands/pr/mod.rs index 577b14bfc..1ac56df86 100644 --- a/lib/crates/fabro-cli/src/commands/pr/mod.rs +++ b/lib/crates/fabro-cli/src/commands/pr/mod.rs @@ -9,37 +9,21 @@ use anyhow::{Context, Result}; use fabro_types::PullRequestRecord; use crate::args::{GlobalArgs, PrCommand, PrNamespace, ServerTargetArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::github::build_github_app_credentials; -use crate::user_config::load_settings; pub(crate) async fn dispatch(ns: PrNamespace, globals: &GlobalArgs) -> Result<()> { + let ctx = CommandContext::base()?; + let github_app = build_github_app_credentials(ctx.machine_settings().app_id())?; match ns.command { PrCommand::Create(args) => { - let cli_settings = load_settings()?; - let github_app = build_github_app_credentials(cli_settings.app_id())?; Box::pin(create::create_command(args, github_app, globals)).await } - PrCommand::List(args) => { - let cli_settings = load_settings()?; - let github_app = build_github_app_credentials(cli_settings.app_id())?; - list::list_command(args, github_app, globals).await - } - PrCommand::View(args) => { - let cli_settings = load_settings()?; - let github_app = build_github_app_credentials(cli_settings.app_id())?; - view::view_command(args, github_app, globals).await - } - PrCommand::Merge(args) => { - let cli_settings = load_settings()?; - let github_app = build_github_app_credentials(cli_settings.app_id())?; - merge::merge_command(args, github_app, globals).await - } - PrCommand::Close(args) => { - let cli_settings = load_settings()?; - let github_app = build_github_app_credentials(cli_settings.app_id())?; - close::close_command(args, github_app, globals).await - } + PrCommand::List(args) => list::list_command(args, github_app, globals).await, + PrCommand::View(args) => view::view_command(args, github_app, globals).await, + PrCommand::Merge(args) => merge::merge_command(args, github_app, globals).await, + PrCommand::Close(args) => close::close_command(args, github_app, globals).await, } } @@ -47,7 +31,8 @@ pub(crate) async fn load_pr_record( server: &ServerTargetArgs, run_id: &str, ) -> Result<(PullRequestRecord, fabro_types::RunId)> { - let lookup = ServerSummaryLookup::connect(server).await?; + let ctx = CommandContext::for_target(server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(run_id)?; let run_id = run.run_id(); let state = lookup.client().get_run_state(&run_id).await?; diff --git a/lib/crates/fabro-cli/src/commands/preflight.rs b/lib/crates/fabro-cli/src/commands/preflight.rs index 55ba46e1c..d15bffb6e 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -3,28 +3,26 @@ use fabro_config::ConfigLayer; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, PreflightArgs}; +use crate::command_context::CommandContext; use crate::commands::run::output::{ api_check_report_to_local, api_diagnostics_to_local, print_preflight_workflow_summary, }; use crate::manifest_builder::{ManifestBuildInput, build_run_manifest, preflight_manifest_args}; -use crate::server_client; use crate::shared::print_json_pretty; -use crate::user_config; pub(crate) async fn execute(mut args: PreflightArgs, globals: &GlobalArgs) -> anyhow::Result<()> { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - let cli_settings = user_config::load_settings()?; - args.verbose = args.verbose || cli_settings.verbose_enabled(); + let ctx = CommandContext::for_target(&args.target)?; + args.verbose = args.verbose || ctx.machine_settings().verbose_enabled(); - let cwd = std::env::current_dir()?; let manifest = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), - cwd, + cwd: ctx.cwd().to_path_buf(), args_layer: ConfigLayer::try_from(&args)?, args: preflight_manifest_args(&args), run_id: None, })?; - let client = server_client::connect_server_only(&args.target).await?; + let client = ctx.server().await?; let response = client.run_preflight(manifest.manifest).await?; let diagnostics = api_diagnostics_to_local(&response.workflow.diagnostics); diff --git a/lib/crates/fabro-cli/src/commands/provider/login.rs b/lib/crates/fabro-cli/src/commands/provider/login.rs index d070573fe..33a6537e0 100644 --- a/lib/crates/fabro-cli/src/commands/provider/login.rs +++ b/lib/crates/fabro-cli/src/commands/provider/login.rs @@ -6,13 +6,14 @@ use fabro_util::terminal::Styles; use tokio::task::spawn_blocking; use crate::args::{GlobalArgs, ProviderLoginArgs}; -use crate::server_client; +use crate::command_context::CommandContext; use crate::shared::provider_auth; pub(super) async fn login_command(args: ProviderLoginArgs, globals: &GlobalArgs) -> Result<()> { globals.require_no_json()?; let s = Styles::detect_stderr(); - let client = server_client::connect_server_backed_api_client(&args.target).await?; + let ctx = CommandContext::for_target(&args.target)?; + let server = ctx.server().await?; let use_oauth = args.provider == Provider::OpenAi && spawn_blocking(|| provider_auth::prompt_confirm("Log in via browser (OAuth)?", true)) @@ -36,7 +37,8 @@ pub(super) async fn login_command(args: ProviderLoginArgs, globals: &GlobalArgs) } for (name, value) in env_pairs { - client + server + .api() .set_secret() .name(name.clone()) .body(types::SetSecretRequest { value }) diff --git a/lib/crates/fabro-cli/src/commands/repo/init.rs b/lib/crates/fabro-cli/src/commands/repo/init.rs index 1023668c9..9595762ab 100644 --- a/lib/crates/fabro-cli/src/commands/repo/init.rs +++ b/lib/crates/fabro-cli/src/commands/repo/init.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail}; use tokio::task::spawn_blocking; use crate::args::{GlobalArgs, RepoInitArgs, ServerTargetArgs}; -use crate::server_client; +use crate::command_context::CommandContext; pub(super) fn git_repo_root() -> Result { let output = std::process::Command::new("git") @@ -166,15 +166,24 @@ async fn check_github_app_installation(target: &ServerTargetArgs) { return; // Not a GitHub repo — skip silently }; - let client = match server_client::connect_server_backed_api_client(target).await { - Ok(client) => client, + let ctx = match CommandContext::for_target(target) { + Ok(ctx) => ctx, + Err(err) => { + eprintln!("\n Warning: could not resolve fabro server settings: {err}"); + return; + } + }; + + let server = match ctx.server().await { + Ok(server) => server, Err(err) => { eprintln!("\n Warning: could not connect to fabro server: {err}"); return; } }; - let check = match client + let check = match server + .api() .get_github_repo() .owner(owner.clone()) .name(repo.clone()) @@ -214,7 +223,8 @@ async fn check_github_app_installation(target: &ServerTargetArgs) { }) .await; - match client + match server + .api() .get_github_repo() .owner(owner.clone()) .name(repo.clone()) diff --git a/lib/crates/fabro-cli/src/commands/run/command.rs b/lib/crates/fabro-cli/src/commands/run/command.rs index 4803fbf03..cc3c054fb 100644 --- a/lib/crates/fabro-cli/src/commands/run/command.rs +++ b/lib/crates/fabro-cli/src/commands/run/command.rs @@ -2,19 +2,19 @@ use anyhow::Result; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, RunArgs}; -use crate::server_client; +use crate::command_context::CommandContext; use crate::shared::print_json_pretty; -use crate::user_config::{self, settings_layer_with_storage_dir}; +use crate::user_config::settings_layer_with_storage_dir; pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<()> { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - let cli_settings = user_config::load_settings()?; + let ctx = CommandContext::for_target(&args.target)?; let cli = settings_layer_with_storage_dir(None)?; - args.verbose = args.verbose || cli_settings.verbose_enabled(); + args.verbose = args.verbose || ctx.machine_settings().verbose_enabled(); let quiet = args.detach; - let prevent_idle_sleep = cli_settings.prevent_idle_sleep_enabled(); - let created_run = Box::pin(super::create::create_run(&args, cli, styles, quiet)).await?; + let prevent_idle_sleep = ctx.machine_settings().prevent_idle_sleep_enabled(); + let created_run = Box::pin(super::create::create_run(&ctx, &args, cli, styles, quiet)).await?; #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep); @@ -22,7 +22,7 @@ pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<( #[cfg(not(feature = "sleep_inhibitor"))] let _ = prevent_idle_sleep; - let client = server_client::connect_server_only(&args.target).await?; + let client = ctx.server().await?; super::start::start_run_with_client(&client, &created_run.run_id, false).await?; if args.detach { diff --git a/lib/crates/fabro-cli/src/commands/run/cp.rs b/lib/crates/fabro-cli/src/commands/run/cp.rs index a63d95b73..99a64f311 100644 --- a/lib/crates/fabro-cli/src/commands/run/cp.rs +++ b/lib/crates/fabro-cli/src/commands/run/cp.rs @@ -5,6 +5,7 @@ use tokio::fs; use tracing::{debug, info}; use crate::args::{CpArgs, GlobalArgs, ServerTargetArgs}; +use crate::command_context::CommandContext; use crate::server_client::ServerStoreClient; use crate::server_runs::ServerSummaryLookup; use crate::shared::{print_json_pretty, split_run_path}; @@ -117,7 +118,8 @@ async fn resolve_client_and_run_id( server: &ServerTargetArgs, run_prefix: &str, ) -> Result<(ServerStoreClient, fabro_types::RunId)> { - let lookup = ServerSummaryLookup::connect(server).await?; + let ctx = CommandContext::for_target(server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(run_prefix)?; Ok((lookup.client().clone_for_reuse(), run.run_id())) } diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index ab37a982f..db6fae923 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -1,14 +1,14 @@ use std::path::PathBuf; use crate::args::RunArgs; +use crate::command_context::CommandContext; use fabro_config::ConfigLayer; use fabro_config::Storage; -use fabro_types::{RunId, Settings}; +use fabro_types::RunId; use fabro_util::terminal::Styles; use super::output::{api_diagnostics_to_local, print_preflight_workflow_summary}; use crate::manifest_builder::{ManifestBuildInput, build_run_manifest, run_manifest_args}; -use crate::server_client; use crate::user_config::{self, ServerTarget}; pub(crate) struct CreatedRun { @@ -20,6 +20,7 @@ pub(crate) struct CreatedRun { /// /// This does NOT execute the workflow — it only prepares the run directory. pub(crate) async fn create_run( + ctx: &CommandContext, args: &RunArgs, cli_defaults: ConfigLayer, styles: &Styles, @@ -30,13 +31,12 @@ pub(crate) async fn create_run( .as_ref() .ok_or_else(|| anyhow::anyhow!("--workflow is required"))?; let cli_args_config = ConfigLayer::try_from(args)?; - let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - let _settings: Settings = cli_args_config + let cwd = ctx.cwd().to_path_buf(); + let _settings = cli_args_config .clone() .combine(ConfigLayer::for_workflow(workflow_path, &cwd)?) .combine(cli_defaults) .resolve()?; - let machine_settings = user_config::load_settings()?; let run_id = args .run_id .as_deref() @@ -51,8 +51,8 @@ pub(crate) async fn create_run( args: run_manifest_args(args), run_id, })?; - let target = user_config::resolve_server_target(&args.target, &machine_settings)?; - let client = server_client::connect_server_only(&args.target).await?; + let target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?; + let client = ctx.server().await?; if !quiet { let preflight = client.run_preflight(built.manifest.clone()).await?; let diagnostics = api_diagnostics_to_local(&preflight.workflow.diagnostics); @@ -67,7 +67,7 @@ pub(crate) async fn create_run( let created_run_id = client.create_run_from_manifest(built.manifest).await?; let local_run_dir = match &target { ServerTarget::UnixSocket(_) => Some( - Storage::new(machine_settings.storage_dir()) + Storage::new(ctx.machine_settings().storage_dir()) .run_scratch(&created_run_id) .root() .to_path_buf(), diff --git a/lib/crates/fabro-cli/src/commands/run/diff.rs b/lib/crates/fabro-cli/src/commands/run/diff.rs index 3db803ffb..81ab88890 100644 --- a/lib/crates/fabro-cli/src/commands/run/diff.rs +++ b/lib/crates/fabro-cli/src/commands/run/diff.rs @@ -4,13 +4,15 @@ use anyhow::{Context, Result, bail}; use tracing::{debug, info}; use crate::args::{DiffArgs, GlobalArgs}; +use crate::command_context::CommandContext; use crate::server_client::RunProjection; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; pub(crate) async fn run(args: DiffArgs, globals: &GlobalArgs) -> Result<()> { info!(run_id = %args.run, "Showing diff"); - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let run_id = run.run_id(); let state = lookup.client().get_run_state(&run_id).await?; diff --git a/lib/crates/fabro-cli/src/commands/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index 4be007b5e..0975ea296 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -6,6 +6,7 @@ use fabro_workflow::operations::{ForkRunInput, RewindTarget, build_timeline_or_r use git2::Repository; use crate::args::{ForkArgs, GlobalArgs}; +use crate::command_context::CommandContext; use crate::commands::store::rebuild::rebuild_run_store; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; @@ -13,7 +14,8 @@ use crate::shared::repo::ensure_matching_repo_origin; pub(crate) async fn run(args: &ForkArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run_id)?; let run_id = run.run_id(); let state = lookup.client().get_run_state(&run_id).await?; diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index 267ceeb99..9cde08d5e 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -11,6 +11,7 @@ use tokio::time; use tracing::{debug, info}; use crate::args::{GlobalArgs, LogsArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::server_runs::ServerSummaryLookup; use crate::shared::format_usd_micros; @@ -18,7 +19,8 @@ use crate::shared::format_usd_micros; const FOLLOW_TERMINAL_GRACE: Duration = Duration::from_millis(500); pub(crate) async fn run(args: &LogsArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let client = lookup.client(); diff --git a/lib/crates/fabro-cli/src/commands/run/mod.rs b/lib/crates/fabro-cli/src/commands/run/mod.rs index 8652ce4ef..30f0f2262 100644 --- a/lib/crates/fabro-cli/src/commands/run/mod.rs +++ b/lib/crates/fabro-cli/src/commands/run/mod.rs @@ -2,6 +2,7 @@ use anyhow::Result; use fabro_util::terminal::Styles; use crate::args::{AttachArgs, GlobalArgs, RunArgs, RunCommands, RunWorkerArgs, StartArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; use crate::user_config::settings_layer_with_storage_dir; @@ -40,7 +41,8 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( apply_json_defaults(&mut args, globals); let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); let cli = settings_layer_with_storage_dir(None)?; - let created_run = Box::pin(create::create_run(&args, cli, styles, true)).await?; + let ctx = CommandContext::for_target(&args.target)?; + let created_run = Box::pin(create::create_run(&ctx, &args, cli, styles, true)).await?; if globals.json { print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?; } else { @@ -49,7 +51,8 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( Ok(()) } RunCommands::Start(StartArgs { server, run }) => { - let lookup = ServerSummaryLookup::connect(&server).await?; + let ctx = CommandContext::for_target(&server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run_info = lookup.resolve(&run)?; let run_id = run_info.run_id(); start::start_run_with_client(lookup.client(), &run_id, false).await?; @@ -60,7 +63,8 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( } RunCommands::Attach(AttachArgs { server, run }) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - let lookup = ServerSummaryLookup::connect(&server).await?; + let ctx = CommandContext::for_target(&server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run_info = lookup.resolve(&run)?; let run_id = run_info.run_id(); let exit_code = attach::attach_run_with_client( @@ -92,8 +96,8 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = { - let cli_settings = crate::user_config::load_settings()?; - crate::sleep_inhibitor::guard(cli_settings.prevent_idle_sleep_enabled()) + let ctx = CommandContext::for_target(&args.server)?; + crate::sleep_inhibitor::guard(ctx.machine_settings().prevent_idle_sleep_enabled()) }; resume::resume_command(args, styles, globals).await } @@ -103,7 +107,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( } RunCommands::Fork(args) => { let styles = Styles::detect_stderr(); - fork::run(&args, &styles, globals).await + Box::pin(fork::run(&args, &styles, globals)).await } RunCommands::Wait(args) => { let styles = Styles::detect_stderr(); diff --git a/lib/crates/fabro-cli/src/commands/run/preview.rs b/lib/crates/fabro-cli/src/commands/run/preview.rs index d6460eb07..ecdd4b3ae 100644 --- a/lib/crates/fabro-cli/src/commands/run/preview.rs +++ b/lib/crates/fabro-cli/src/commands/run/preview.rs @@ -2,11 +2,13 @@ use anyhow::{Context, Result}; use tracing::info; use crate::args::{GlobalArgs, PreviewArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let run_id = run.run_id(); let expires_in_secs = diff --git a/lib/crates/fabro-cli/src/commands/run/resume.rs b/lib/crates/fabro-cli/src/commands/run/resume.rs index da2f8b4e9..b79e52c21 100644 --- a/lib/crates/fabro-cli/src/commands/run/resume.rs +++ b/lib/crates/fabro-cli/src/commands/run/resume.rs @@ -1,6 +1,7 @@ use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, ResumeArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; @@ -14,7 +15,8 @@ pub(crate) async fn resume_command( styles: &'static Styles, globals: &GlobalArgs, ) -> anyhow::Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let run_id = run.run_id(); diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index e85242f59..f5d027abd 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -14,6 +14,7 @@ use git2::Repository; use serde::Serialize; use crate::args::{GlobalArgs, RewindArgs}; +use crate::command_context::CommandContext; use crate::commands::store::rebuild::rebuild_run_store; use crate::server_client::ServerStoreClient; use crate::server_runs::ServerSummaryLookup; @@ -30,7 +31,8 @@ pub(crate) struct TimelineEntryJson { pub(crate) async fn run(args: &RewindArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run_id)?; let run_id = run.run_id(); let state = lookup.client().get_run_state(&run_id).await?; diff --git a/lib/crates/fabro-cli/src/commands/run/ssh.rs b/lib/crates/fabro-cli/src/commands/run/ssh.rs index f531cc7b8..725622c24 100644 --- a/lib/crates/fabro-cli/src/commands/run/ssh.rs +++ b/lib/crates/fabro-cli/src/commands/run/ssh.rs @@ -2,6 +2,7 @@ use anyhow::{Result, bail}; use tracing::info; use crate::args::{GlobalArgs, SshArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::print_json_pretty; @@ -10,7 +11,8 @@ pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs) -> Result<()> { globals.require_no_json()?; } - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let run_id = run.run_id(); let ssh = lookup diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index a457f16e2..198c0a9fe 100644 --- a/lib/crates/fabro-cli/src/commands/run/wait.rs +++ b/lib/crates/fabro-cli/src/commands/run/wait.rs @@ -8,6 +8,7 @@ use fabro_workflow::run_status::RunStatus; use tracing::info; use crate::args::{GlobalArgs, WaitArgs}; +use crate::command_context::CommandContext; use crate::server_runs::ServerSummaryLookup; use crate::shared::{format_duration_ms, format_usd_micros}; @@ -17,7 +18,8 @@ const WAIT_STARTUP_GRACE: std::time::Duration = std::time::Duration::from_millis const WAIT_STARTUP_GRACE: std::time::Duration = std::time::Duration::from_secs(3); pub(crate) async fn run(args: &WaitArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run_info = lookup.resolve(&args.run)?; let client = lookup.client(); diff --git a/lib/crates/fabro-cli/src/commands/runs/inspect.rs b/lib/crates/fabro-cli/src/commands/runs/inspect.rs index 6558fad58..c3192e8c3 100644 --- a/lib/crates/fabro-cli/src/commands/runs/inspect.rs +++ b/lib/crates/fabro-cli/src/commands/runs/inspect.rs @@ -4,6 +4,7 @@ use serde::Serialize; use fabro_workflow::run_status::RunStatus; use crate::args::{GlobalArgs, InspectArgs}; +use crate::command_context::CommandContext; use crate::server_client::RunProjection; use crate::server_runs::{ServerRunSummaryInfo, ServerSummaryLookup}; @@ -19,7 +20,8 @@ pub(crate) struct InspectOutput { } pub(crate) async fn run(args: &InspectArgs, _globals: &GlobalArgs) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let run = lookup.resolve(&args.run)?; let run_id = run.run_id(); let state = lookup.client().get_run_state(&run_id).await?; diff --git a/lib/crates/fabro-cli/src/commands/runs/list.rs b/lib/crates/fabro-cli/src/commands/runs/list.rs index d75761a7a..e3e3de71e 100644 --- a/lib/crates/fabro-cli/src/commands/runs/list.rs +++ b/lib/crates/fabro-cli/src/commands/runs/list.rs @@ -10,6 +10,7 @@ use fabro_util::text::strip_goal_decoration; use fabro_workflow::run_status::RunStatus; use crate::args::{GlobalArgs, RunsListArgs}; +use crate::command_context::CommandContext; use crate::server_runs::{ServerSummaryLookup, filter_server_runs}; use crate::shared::{color_if, format_duration_ms, tilde_path}; @@ -21,7 +22,8 @@ pub(crate) async fn list_command( styles: &Styles, globals: &GlobalArgs, ) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; let label_filters = parse_label_filters(&args.filter.label); let filtered = filter_server_runs( lookup.runs(), diff --git a/lib/crates/fabro-cli/src/commands/runs/rm.rs b/lib/crates/fabro-cli/src/commands/runs/rm.rs index 36e435fe2..3d488e61e 100644 --- a/lib/crates/fabro-cli/src/commands/runs/rm.rs +++ b/lib/crates/fabro-cli/src/commands/runs/rm.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result, bail}; use crate::args::{GlobalArgs, RunsRemoveArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::server_runs::{ ServerRunSummaryInfo, ServerSummaryLookup, resolve_server_run_from_summaries, @@ -10,7 +11,8 @@ use crate::shared::print_json_pretty; use super::short_run_id; pub(crate) async fn remove_command(args: &RunsRemoveArgs, globals: &GlobalArgs) -> Result<()> { - let lookup = ServerSummaryLookup::connect(&args.server).await?; + let ctx = CommandContext::for_target(&args.server)?; + let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?; remove_from(args, lookup.client(), lookup.runs(), globals).await } diff --git a/lib/crates/fabro-cli/src/commands/secret/list.rs b/lib/crates/fabro-cli/src/commands/secret/list.rs index a599e2d58..ad2dcf879 100644 --- a/lib/crates/fabro-cli/src/commands/secret/list.rs +++ b/lib/crates/fabro-cli/src/commands/secret/list.rs @@ -2,6 +2,7 @@ use anyhow::Result; use fabro_api::Client; use crate::args::{GlobalArgs, SecretListArgs}; +use crate::server_client; use crate::shared::print_json_pretty; pub(super) async fn list_command( @@ -13,7 +14,7 @@ pub(super) async fn list_command( .list_secrets() .send() .await - .map_err(super::map_api_error)?; + .map_err(server_client::map_api_error)?; let secrets = response.into_inner().data; if globals.json { print_json_pretty(&secrets)?; diff --git a/lib/crates/fabro-cli/src/commands/secret/mod.rs b/lib/crates/fabro-cli/src/commands/secret/mod.rs index f91b692dd..fde139022 100644 --- a/lib/crates/fabro-cli/src/commands/secret/mod.rs +++ b/lib/crates/fabro-cli/src/commands/secret/mod.rs @@ -2,43 +2,17 @@ mod list; mod rm; mod set; -use anyhow::{Result, anyhow}; +use anyhow::Result; use crate::args::{GlobalArgs, SecretCommand, SecretNamespace}; -use crate::server_client; - -fn map_api_error(err: progenitor_client::Error) -> anyhow::Error -where - E: serde::Serialize + std::fmt::Debug, -{ - match err { - progenitor_client::Error::ErrorResponse(response) => { - let status = response.status(); - if let Ok(value) = serde_json::to_value(response.into_inner()) { - if let Some(detail) = value - .get("errors") - .and_then(serde_json::Value::as_array) - .and_then(|errors| errors.first()) - .and_then(|entry| entry.get("detail")) - .and_then(serde_json::Value::as_str) - { - return anyhow!("{detail}"); - } - } - anyhow!("request failed with status {status}") - } - progenitor_client::Error::UnexpectedResponse(response) => { - anyhow!("request failed with status {}", response.status()) - } - other => anyhow!("{other}"), - } -} +use crate::command_context::CommandContext; pub(crate) async fn dispatch(ns: SecretNamespace, globals: &GlobalArgs) -> Result<()> { - let client = server_client::connect_server_backed_api_client(&ns.target).await?; + let ctx = CommandContext::for_target(&ns.target)?; + let server = ctx.server().await?; match ns.command { - SecretCommand::List(args) => list::list_command(&client, &args, globals).await, - SecretCommand::Rm(args) => rm::rm_command(&client, &args, globals).await, - SecretCommand::Set(args) => set::set_command(&client, &args, globals).await, + SecretCommand::List(args) => list::list_command(server.api(), &args, globals).await, + SecretCommand::Rm(args) => rm::rm_command(server.api(), &args, globals).await, + SecretCommand::Set(args) => set::set_command(server.api(), &args, globals).await, } } diff --git a/lib/crates/fabro-cli/src/commands/secret/rm.rs b/lib/crates/fabro-cli/src/commands/secret/rm.rs index 2f3f948c6..376abe5a6 100644 --- a/lib/crates/fabro-cli/src/commands/secret/rm.rs +++ b/lib/crates/fabro-cli/src/commands/secret/rm.rs @@ -2,6 +2,7 @@ use anyhow::Result; use fabro_api::Client; use crate::args::{GlobalArgs, SecretRmArgs}; +use crate::server_client; use crate::shared::print_json_pretty; pub(super) async fn rm_command( @@ -14,7 +15,7 @@ pub(super) async fn rm_command( .name(args.key.clone()) .send() .await - .map_err(super::map_api_error)?; + .map_err(server_client::map_api_error)?; if globals.json { print_json_pretty(&serde_json::json!({ "key": args.key }))?; } else { diff --git a/lib/crates/fabro-cli/src/commands/secret/set.rs b/lib/crates/fabro-cli/src/commands/secret/set.rs index da04d10d9..9ea94ab5b 100644 --- a/lib/crates/fabro-cli/src/commands/secret/set.rs +++ b/lib/crates/fabro-cli/src/commands/secret/set.rs @@ -2,6 +2,7 @@ use anyhow::Result; use fabro_api::{Client, types}; use crate::args::{GlobalArgs, SecretSetArgs}; +use crate::server_client; use crate::shared::print_json_pretty; pub(super) async fn set_command( @@ -17,7 +18,7 @@ pub(super) async fn set_command( }) .send() .await - .map_err(super::map_api_error)? + .map_err(server_client::map_api_error)? .into_inner(); if globals.json { print_json_pretty(&meta)?; diff --git a/lib/crates/fabro-cli/src/commands/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index 9defef074..5c970336a 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -5,16 +5,15 @@ use cli_table::{Cell, CellStruct, Style, Table}; use fabro_api::types; use crate::args::{DfArgs, GlobalArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::shared::{format_size, print_json_pretty}; pub(super) async fn df_command(args: &DfArgs, globals: &GlobalArgs) -> Result<()> { - let client = server_client::connect_server_backed_api_client_with_storage_dir( - &args.connection.target, - args.connection.storage_dir.as_deref(), - ) - .await?; - let output = client + let ctx = CommandContext::for_connection(&args.connection)?; + let server = ctx.server().await?; + let output = server + .api() .get_system_disk_usage() .verbose(args.verbose) .send() @@ -25,7 +24,8 @@ pub(super) async fn df_command(args: &DfArgs, globals: &GlobalArgs) -> Result<() let storage_dir = if globals.json { None } else { - client + server + .api() .get_system_info() .send() .await diff --git a/lib/crates/fabro-cli/src/commands/system/events.rs b/lib/crates/fabro-cli/src/commands/system/events.rs index 0285fc4e4..096eddb4c 100644 --- a/lib/crates/fabro-cli/src/commands/system/events.rs +++ b/lib/crates/fabro-cli/src/commands/system/events.rs @@ -2,17 +2,15 @@ use anyhow::Result; use futures::StreamExt; use crate::args::{GlobalArgs, SystemEventsArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::sse; pub(super) async fn events_command(args: &SystemEventsArgs, globals: &GlobalArgs) -> Result<()> { - let client = server_client::connect_server_backed_api_client_with_storage_dir( - &args.connection.target, - args.connection.storage_dir.as_deref(), - ) - .await?; + let ctx = CommandContext::for_connection(&args.connection)?; + let server = ctx.server().await?; - let mut request = client.attach_events(); + let mut request = server.api().attach_events(); if !args.run_ids.is_empty() { request = request.run_id(args.run_ids.join(",")); } diff --git a/lib/crates/fabro-cli/src/commands/system/info.rs b/lib/crates/fabro-cli/src/commands/system/info.rs index e0a4b9003..23934d6a4 100644 --- a/lib/crates/fabro-cli/src/commands/system/info.rs +++ b/lib/crates/fabro-cli/src/commands/system/info.rs @@ -1,16 +1,15 @@ use anyhow::Result; use crate::args::{GlobalArgs, SystemInfoArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::shared::print_json_pretty; pub(super) async fn info_command(args: &SystemInfoArgs, globals: &GlobalArgs) -> Result<()> { - let client = server_client::connect_server_backed_api_client_with_storage_dir( - &args.connection.target, - args.connection.storage_dir.as_deref(), - ) - .await?; - let response = client + let ctx = CommandContext::for_connection(&args.connection)?; + let server = ctx.server().await?; + let response = server + .api() .get_system_info() .send() .await diff --git a/lib/crates/fabro-cli/src/commands/system/prune.rs b/lib/crates/fabro-cli/src/commands/system/prune.rs index b86329751..98bab80ef 100644 --- a/lib/crates/fabro-cli/src/commands/system/prune.rs +++ b/lib/crates/fabro-cli/src/commands/system/prune.rs @@ -6,16 +6,15 @@ use tracing::{debug, info}; use fabro_api::types; use crate::args::{GlobalArgs, RunsPruneArgs}; +use crate::command_context::CommandContext; use crate::server_client; use crate::shared::{format_size, print_json_pretty}; pub(super) async fn prune_command(args: &RunsPruneArgs, globals: &GlobalArgs) -> Result<()> { - let client = server_client::connect_server_backed_api_client_with_storage_dir( - &args.connection.target, - args.connection.storage_dir.as_deref(), - ) - .await?; - let response = client + let ctx = CommandContext::for_connection(&args.connection)?; + let server = ctx.server().await?; + let response = server + .api() .prune_runs() .body(types::PruneRunsRequest { before: args.filter.before.clone(), diff --git a/lib/crates/fabro-cli/src/commands/validate.rs b/lib/crates/fabro-cli/src/commands/validate.rs index 6050de7b8..fe1a7fab6 100644 --- a/lib/crates/fabro-cli/src/commands/validate.rs +++ b/lib/crates/fabro-cli/src/commands/validate.rs @@ -3,9 +3,9 @@ use fabro_config::ConfigLayer; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, ValidateArgs}; +use crate::command_context::CommandContext; use crate::commands::run::output::api_diagnostics_to_local; use crate::manifest_builder::{ManifestBuildInput, build_run_manifest}; -use crate::server_client; use crate::shared::{print_diagnostics, print_json_pretty, relative_path}; pub(crate) async fn run( @@ -13,15 +13,15 @@ pub(crate) async fn run( styles: &Styles, globals: &GlobalArgs, ) -> anyhow::Result<()> { - let cwd = std::env::current_dir()?; + let ctx = CommandContext::for_target(&args.target)?; let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), - cwd, + cwd: ctx.cwd().to_path_buf(), args_layer: ConfigLayer::default(), args: None, run_id: None, })?; - let client = server_client::connect_server_only(&args.target).await?; + let client = ctx.server().await?; let response = client.run_preflight(built.manifest).await?; let diagnostics = api_diagnostics_to_local(&response.workflow.diagnostics); diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 5fb95fbd9..37a453d35 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)] mod args; +mod command_context; mod commands; mod logging; mod manifest_builder; diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index 14b86167e..410331208 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -97,11 +97,14 @@ pub(crate) async fn connect_server_target_direct(target: &str) -> Result Result { - let settings = user_config::load_settings()?; - let target = user_config::resolve_server_target(args, &settings)?; +pub(crate) async fn connect_server_with_settings( + args: &ServerTargetArgs, + settings: &Settings, + base_config_path: &Path, +) -> Result { + let target = user_config::resolve_server_target(args, settings)?; let runtime = LocalServerRuntime { - active_config_path: user_config::active_settings_path(None), + active_config_path: base_config_path.to_path_buf(), storage_dir: settings.storage_dir(), }; connect_target_api_client_bundle(&target, &runtime).await @@ -125,15 +128,6 @@ pub(crate) async fn connect_api_client(storage_dir: &Path) -> Result Result { - connect_target_api_client_bundle(target, runtime) - .await - .map(|client| client.client) -} - async fn connect_target_api_client_bundle( target: &user_config::ServerTarget, runtime: &LocalServerRuntime, @@ -158,25 +152,6 @@ async fn connect_target_api_client_bundle( } } -pub(crate) async fn connect_server_backed_api_client( - args: &ServerTargetArgs, -) -> Result { - connect_server_backed_api_client_with_storage_dir(args, None).await -} - -pub(crate) async fn connect_server_backed_api_client_with_storage_dir( - args: &ServerTargetArgs, - storage_dir: Option<&Path>, -) -> Result { - let settings = user_config::load_settings_with_storage_dir(storage_dir)?; - let target = user_config::resolve_server_target(args, &settings)?; - let runtime = LocalServerRuntime { - active_config_path: user_config::active_settings_path(None), - storage_dir: settings.storage_dir(), - }; - connect_target_api_client(&target, &runtime).await -} - fn connect_remote_api_client_bundle( api_url: &str, tls: Option<&user_config::ClientTlsSettings>, @@ -288,6 +263,20 @@ impl ServerStoreClient { self.clone() } + pub(crate) fn api(&self) -> &fabro_api::Client { + &self.client + } + + #[allow(dead_code)] + pub(crate) fn http_client(&self) -> &reqwest::Client { + &self.http_client + } + + #[allow(dead_code)] + pub(crate) fn base_url(&self) -> &str { + &self.base_url + } + pub(crate) async fn retrieve_server_settings(&self) -> Result { let response = self .client diff --git a/lib/crates/fabro-cli/src/server_runs.rs b/lib/crates/fabro-cli/src/server_runs.rs index 344b01098..c1c67b75a 100644 --- a/lib/crates/fabro-cli/src/server_runs.rs +++ b/lib/crates/fabro-cli/src/server_runs.rs @@ -1,4 +1,5 @@ use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::collections::HashMap; @@ -8,7 +9,6 @@ use fabro_store::RunSummary; use fabro_types::{RunId, RunStatus, StatusReason}; use fabro_workflow::run_lookup::{RunInfo, resolve_run_from_summaries, scratch_base}; -use crate::args::ServerTargetArgs; use crate::server_client::{self, ServerStoreClient}; pub(crate) struct ServerRunLookup { @@ -105,13 +105,12 @@ impl ServerRunSummaryInfo { } pub(crate) struct ServerSummaryLookup { - client: ServerStoreClient, + client: Arc, runs: Vec, } impl ServerSummaryLookup { - pub(crate) async fn connect(args: &ServerTargetArgs) -> Result { - let client = server_client::connect_server_only(args).await?; + pub(crate) async fn from_client(client: Arc) -> Result { let summaries = client.list_store_runs().await?; let mut runs = summaries .into_iter() @@ -126,7 +125,7 @@ impl ServerSummaryLookup { } pub(crate) fn client(&self) -> &ServerStoreClient { - &self.client + self.client.as_ref() } pub(crate) fn runs(&self) -> &[ServerRunSummaryInfo] {