refactor(cli): centralize command settings and server access

Add CommandContext to load machine settings once per invocation, cache
server access, and route migrated commands through the shared
ServerStoreClient path instead of reloading settings and reconnecting ad
hoc.
This commit is contained in:
Bryan Helmkamp 2026-04-08 16:27:25 -04:00
parent 911831fc23
commit 367fd9302b
39 changed files with 345 additions and 232 deletions

View file

@ -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<String>,
},
ByStorageDir {
target_override: Option<String>,
storage_dir_override: Option<PathBuf>,
},
}
pub(crate) struct CommandContext {
cwd: PathBuf,
base_config_path: PathBuf,
machine_settings: Settings,
server_mode: ServerMode,
server: OnceCell<Arc<ServerStoreClient>>,
}
impl CommandContext {
pub(crate) fn base() -> Result<Self> {
Self::new(ServerMode::None)
}
pub(crate) fn for_target(args: &ServerTargetArgs) -> Result<Self> {
Self::new(ServerMode::ByTarget {
target_override: args.server.clone(),
})
}
pub(crate) fn for_connection(args: &ServerConnectionArgs) -> Result<Self> {
Self::new(ServerMode::ByStorageDir {
target_override: args.target.server.clone(),
storage_dir_override: args.storage_dir.clone_path(),
})
}
fn new(server_mode: ServerMode) -> Result<Self> {
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<Arc<ServerStoreClient>> {
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))
}
}

View file

@ -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<u32>,
) -> Result<(RunId, ServerStoreClient, Vec<ArtifactEntry>)> {
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();

View file

@ -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<EffectiveSettingsLayers> {
let cwd = std::env::current_dir()?;
fn config_layers(
ctx: &CommandContext,
workflow: Option<&Path>,
) -> anyhow::Result<EffectiveSettingsLayers> {
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<Settings> {
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<Settings> {
);
}
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,

View file

@ -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

View file

@ -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);

View file

@ -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<ModelsCommand>, 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<E>(err: progenitor_client::Error<E>) -> 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<TInput, TOutput>(value: TInput) -> Result<TOutput>
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<Model>>(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,

View file

@ -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<fabro_github::GitHubAppCredentials>,
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?;

View file

@ -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() {

View file

@ -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?;

View file

@ -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);

View file

@ -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 })

View file

@ -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<PathBuf> {
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())

View file

@ -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 {

View file

@ -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()))
}

View file

@ -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(),

View file

@ -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?;

View file

@ -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?;

View file

@ -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();

View file

@ -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();

View file

@ -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 =

View file

@ -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();

View file

@ -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?;

View file

@ -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

View file

@ -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();

View file

@ -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?;

View file

@ -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(),

View file

@ -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
}

View file

@ -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)?;

View file

@ -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<E>(err: progenitor_client::Error<E>) -> 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,
}
}

View file

@ -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 {

View file

@ -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)?;

View file

@ -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

View file

@ -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(","));
}

View file

@ -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

View file

@ -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(),

View file

@ -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);

View file

@ -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;

View file

@ -97,11 +97,14 @@ pub(crate) async fn connect_server_target_direct(target: &str) -> Result<ServerS
}
}
pub(crate) async fn connect_server_only(args: &ServerTargetArgs) -> Result<ServerStoreClient> {
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<ServerStoreClient> {
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<fabro_api::
.map(|client| client.client)
}
async fn connect_target_api_client(
target: &user_config::ServerTarget,
runtime: &LocalServerRuntime,
) -> Result<fabro_api::Client> {
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<fabro_api::Client> {
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<fabro_api::Client> {
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<Settings> {
let response = self
.client

View file

@ -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<ServerStoreClient>,
runs: Vec<ServerRunSummaryInfo>,
}
impl ServerSummaryLookup {
pub(crate) async fn connect(args: &ServerTargetArgs) -> Result<Self> {
let client = server_client::connect_server_only(args).await?;
pub(crate) async fn from_client(client: Arc<ServerStoreClient>) -> Result<Self> {
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] {