From 79a366022f4d3e9176c9945f203be57968d36b57 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 29 Mar 2026 20:58:46 -0400 Subject: [PATCH] Infer CLI mode from storage and server flags --- docs/administration/deploy-server.mdx | 6 +- docs/changelog/2026-03-07.mdx | 12 +-- docs/reference/cli-configuration.mdx | 12 +-- docs/reference/cli.mdx | 11 +-- docs/reference/run-directory.mdx | 2 +- lib/crates/fabro-cli/src/args.rs | 31 ++----- lib/crates/fabro-cli/src/cli_config.rs | 81 ++++++++++++------- lib/crates/fabro-cli/src/commands/asset/cp.rs | 8 +- .../fabro-cli/src/commands/asset/list.rs | 8 +- .../fabro-cli/src/commands/asset/mod.rs | 8 +- .../fabro-cli/src/commands/config/mod.rs | 18 +++-- lib/crates/fabro-cli/src/commands/exec.rs | 4 +- lib/crates/fabro-cli/src/commands/llm/chat.rs | 2 +- lib/crates/fabro-cli/src/commands/llm/mod.rs | 4 +- .../fabro-cli/src/commands/llm/prompt.rs | 2 +- lib/crates/fabro-cli/src/commands/model.rs | 4 +- lib/crates/fabro-cli/src/commands/pr/close.rs | 7 +- .../fabro-cli/src/commands/pr/create.rs | 12 +-- lib/crates/fabro-cli/src/commands/pr/list.rs | 10 ++- lib/crates/fabro-cli/src/commands/pr/merge.rs | 7 +- lib/crates/fabro-cli/src/commands/pr/mod.rs | 21 ++--- lib/crates/fabro-cli/src/commands/pr/view.rs | 7 +- .../fabro-cli/src/commands/preflight.rs | 12 ++- .../fabro-cli/src/commands/run/attach.rs | 46 +++++------ .../fabro-cli/src/commands/run/command.rs | 8 +- lib/crates/fabro-cli/src/commands/run/cp.rs | 8 +- .../fabro-cli/src/commands/run/create.rs | 6 +- .../fabro-cli/src/commands/run/detached.rs | 3 +- lib/crates/fabro-cli/src/commands/run/diff.rs | 13 +-- lib/crates/fabro-cli/src/commands/run/fork.rs | 8 +- lib/crates/fabro-cli/src/commands/run/logs.rs | 33 ++++---- lib/crates/fabro-cli/src/commands/run/mod.rs | 34 ++++---- .../fabro-cli/src/commands/run/overrides.rs | 1 - .../fabro-cli/src/commands/run/preview.rs | 15 ++-- .../fabro-cli/src/commands/run/resume.rs | 10 ++- .../fabro-cli/src/commands/run/rewind.rs | 8 +- lib/crates/fabro-cli/src/commands/run/ssh.rs | 15 ++-- lib/crates/fabro-cli/src/commands/run/wait.rs | 14 ++-- .../fabro-cli/src/commands/runs/inspect.rs | 27 +++---- .../fabro-cli/src/commands/runs/list.rs | 15 ++-- lib/crates/fabro-cli/src/commands/runs/mod.rs | 10 +-- lib/crates/fabro-cli/src/commands/runs/rm.rs | 18 ++--- .../fabro-cli/src/commands/store/dump.rs | 8 +- .../fabro-cli/src/commands/store/mod.rs | 6 +- .../fabro-cli/src/commands/system/df.rs | 11 +-- .../fabro-cli/src/commands/system/mod.rs | 8 +- .../fabro-cli/src/commands/system/prune.rs | 11 +-- lib/crates/fabro-cli/src/main.rs | 57 +++++++++++-- .../snapshots/it__cli__serve_help.snap | 14 ++-- lib/crates/fabro-server/src/serve.rs | 8 +- lib/crates/fabro-server/src/server.rs | 7 +- 51 files changed, 395 insertions(+), 316 deletions(-) rename lib/crates/fabro-cli/tests/{ => it}/snapshots/it__cli__serve_help.snap (82%) diff --git a/docs/administration/deploy-server.mdx b/docs/administration/deploy-server.mdx index cb40f4786..dce14f8bd 100644 --- a/docs/administration/deploy-server.mdx +++ b/docs/administration/deploy-server.mdx @@ -119,13 +119,13 @@ mode = "server" base_url = "https://fabro.example.com:3000" ``` -Or use the `--mode` flag: +Or use the `--server-url` flag: ```bash -fabro --mode server --server-url https://fabro.example.com:3000 models list +fabro --server-url https://fabro.example.com:3000 model list ``` -This applies to commands like `fabro models list`, `fabro llm chat`, and `fabro exec`. See [CLI Configuration](/reference/cli-configuration#mode) for the full options including mTLS setup. +This applies to commands like `fabro model list`, `fabro llm chat`, and `fabro exec`. See [CLI Configuration](/reference/cli-configuration#mode) for the full options including mTLS setup. ## Next steps diff --git a/docs/changelog/2026-03-07.mdx b/docs/changelog/2026-03-07.mdx index 762f75c3e..5200968b8 100644 --- a/docs/changelog/2026-03-07.mdx +++ b/docs/changelog/2026-03-07.mdx @@ -10,8 +10,8 @@ Fabro now exposes a `POST /completions` endpoint for single-turn LLM completions The CLI's `fabro llm` commands can now target the server instead of calling providers directly: ```bash -fabro llm prompt "Summarize this file" --mode server -fabro llm chat --mode server +fabro llm prompt "Summarize this file" --server-url http://localhost:3000 +fabro llm chat --server-url http://localhost:3000 ``` ## Two new sandbox providers: exe.dev and Sprites @@ -63,14 +63,14 @@ To migrate, regenerate your TypeScript client and update any direct API calls. - New `POST /completions` endpoint for single-turn LLM completions with SSE streaming and structured output via JSON Schema - New `GET /models` endpoint exposes the full LLM model catalog with pagination - New `POST /models/{id}/test` endpoint for testing model connectivity in server mode -- Session endpoints for interactive LLM chat via `fabro llm chat --mode server` +- Session endpoints for interactive LLM chat via `fabro llm chat --server-url http://localhost:3000` - Verification API reorganized: `/verifications` split into `/verification/criteria` and `/verification/controls` -- `fabro llm prompt --mode server` routes prompts through the Fabro server -- `fabro llm chat --mode server` enables interactive chat sessions through the server -- `fabro model list --mode standalone|server` controls whether model list comes from local config or server +- `fabro llm prompt --server-url ` routes prompts through the Fabro server +- `fabro llm chat --server-url ` enables interactive chat sessions through the server +- `fabro model list --server-url ` fetches the model list from the Fabro server - Added `--goal` arg to `fabro run start` to override the workflow goal from the command line - Turn and tool-call counts now display correctly in non-TTY mode diff --git a/docs/reference/cli-configuration.mdx b/docs/reference/cli-configuration.mdx index 382efed3a..2988c77a3 100644 --- a/docs/reference/cli-configuration.mdx +++ b/docs/reference/cli-configuration.mdx @@ -149,18 +149,14 @@ Customize the git author identity used for checkpoint commits. Overrides the ser ## `mode` -Controls whether commands run in-process or delegate to a running Fabro API server. +Controls the default execution mode when neither `--storage-dir` nor `--server-url` is passed. | Value | Description | |---|---| | `"standalone"` | Execute locally (default) | | `"server"` | Delegate to an Fabro API server | -Override with the `--mode` CLI flag: - -```bash -fabro --mode server models list -``` +For a single invocation, `--storage-dir` implies standalone mode and `--server-url` implies server mode. ## `[server]` section @@ -170,10 +166,10 @@ Configuration for server mode. |---|---|---| | `base_url` | Server URL | `"http://localhost:3000"` | -Override the URL with the `--server-url` CLI flag: +Passing `--server-url` implies server mode and overrides `server.base_url`: ```bash -fabro --mode server --server-url https://fabro.example.com:3000 models list +fabro --server-url https://fabro.example.com:3000 model list ``` ### `[server.tls]` section diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index cbace6df0..753af2bd9 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -11,6 +11,8 @@ These flags apply to all subcommands: |---|---| | `--debug` | Enable DEBUG-level logging (default is INFO) | | `--no-upgrade-check` | Skip the automatic background upgrade check | +| `--storage-dir ` | Storage directory for local run data (default: `~/.fabro`). Implies standalone mode. | +| `--server-url ` | Fabro API server URL (overrides `server.base_url` from `cli.toml`). Implies server mode. | | `-h, --help` | Print help | | `-V, --version` | Print version | @@ -70,7 +72,6 @@ fabro run run.toml | Argument / Flag | Description | |---|---| | `` | Path to a `.fabro` workflow file, `.toml` task config, or workflow name (resolved from `fabro/workflows/` in the project, then `~/.fabro/workflows/`). | -| `--storage-dir ` | Storage directory (default: `~/.fabro`) | | `--dry-run` | Execute with a simulated LLM backend | | `--auto-approve` | Auto-approve all human gates | | `--model ` | Override default LLM model | @@ -249,8 +250,6 @@ fabro exec "Refactor the auth module" --permissions full --auto-approve | `--verbose` | Print full LLM request/response JSON to stderr | — | | `--skills-dir ` | Directory containing skill files (overrides default discovery) | — | | `--output-format ` | Output format: `text` (human-readable) or `json` (NDJSON event stream) | `text` | -| `--mode ` | `standalone` (default) or `server` — in server mode, routes through the Fabro API's `/completions` endpoint | `standalone` | -| `--server-url ` | Fabro API server URL (overrides `server.base_url` from `cli.toml`) | — | Permission levels control which tools are auto-approved: `read-only` allows read tools (`read_file`, `grep`, `glob`, `list_dir`) and subagent tools; `read-write` adds write tools (`write_file`, `edit_file`, `apply_patch`); `full` allows all tools including shell commands. Tools outside the permission level are either interactively prompted (if a TTY is present) or denied (with `--auto-approve`). See [default models by provider](/core-concepts/models#default-models). @@ -284,14 +283,13 @@ Start an interactive multi-turn chat session. In server mode, the session is bac ```bash fabro llm chat fabro llm chat -m claude-opus-4-6 -s "You are a helpful coding assistant" -fabro llm chat --mode server +fabro llm chat --server-url http://localhost:3000 ``` | Flag | Description | |---|---| | `-m, --model ` | Model to use | | `-s, --system ` | System prompt | -| `--mode ` | `standalone` (default) or `server` — in server mode, routes through the Fabro API | --- @@ -303,14 +301,13 @@ List available LLM models from the built-in catalog. Running `fabro model` with fabro model list fabro model list -p anthropic fabro model list -q sonnet -fabro model list --mode server +fabro model list --server-url http://localhost:3000 ``` | Flag | Description | |---|---| | `-p, --provider ` | Filter by provider | | `-q, --query ` | Search for models matching this string (checks ID, display name, and aliases) | -| `--mode ` | `standalone` (default) or `server` — in server mode, fetches the catalog from the Fabro API | ## `fabro model test` diff --git a/docs/reference/run-directory.mdx b/docs/reference/run-directory.mdx index 5d4e66e89..dcbd6ac72 100644 --- a/docs/reference/run-directory.mdx +++ b/docs/reference/run-directory.mdx @@ -13,7 +13,7 @@ Each `fabro run` invocation creates a timestamped directory under `~/.fabro/runs ~/.fabro/runs/20260307-01JQXYZ123ABC456DEF789/ ``` -The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the base storage directory with `--storage-dir` (the runs directory will be `/runs/`). +The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the base storage directory with the global `--storage-dir` flag (the runs directory will be `/runs/`). ## Root-level files diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs index abc29dcb1..9d9379b88 100644 --- a/lib/crates/fabro-cli/src/args.rs +++ b/lib/crates/fabro-cli/src/args.rs @@ -6,9 +6,6 @@ use fabro_agent::cli::AgentArgs; use fabro_graphviz::render::GraphFormat; use fabro_llm::cli::{ChatArgs, ModelsCommand, PromptArgs}; -#[cfg(feature = "server")] -use crate::cli_config; - pub(crate) const LONG_VERSION: &str = concat!( env!("CARGO_PKG_VERSION"), " (", @@ -36,26 +33,14 @@ pub(crate) struct GlobalArgs { #[arg(long, global = true, conflicts_with = "quiet")] pub verbose: bool, - /// Execution mode: standalone (in-process) or server (delegate to API) - #[cfg(feature = "server")] - #[arg(long, global = true, value_parser = parse_execution_mode)] - pub mode: Option, - - /// Server URL (overrides server.base_url from cli.toml) - #[cfg(feature = "server")] + /// Storage directory (default: ~/.fabro) #[arg(long, global = true)] - pub server_url: Option, -} + pub storage_dir: Option, -#[cfg(feature = "server")] -pub(crate) fn parse_execution_mode(s: &str) -> Result { - match s { - "standalone" => Ok(cli_config::ExecutionMode::Standalone), - "server" => Ok(cli_config::ExecutionMode::Server), - _ => Err(format!( - "invalid mode '{s}', expected 'standalone' or 'server'" - )), - } + #[cfg(feature = "server")] + /// Server URL (overrides server.base_url from cli.toml) + #[arg(long, global = true, conflicts_with = "storage_dir")] + pub server_url: Option, } #[derive(Debug, Clone, Copy, ValueEnum)] @@ -102,10 +87,6 @@ pub(crate) struct RunArgs { #[arg(required = true)] pub(crate) workflow: Option, - /// Storage directory (default: ~/.fabro) - #[arg(long)] - pub(crate) storage_dir: Option, - /// Execute with simulated LLM backend #[arg(long)] pub(crate) dry_run: bool, diff --git a/lib/crates/fabro-cli/src/cli_config.rs b/lib/crates/fabro-cli/src/cli_config.rs index dabedc41f..9be414a5b 100644 --- a/lib/crates/fabro-cli/src/cli_config.rs +++ b/lib/crates/fabro-cli/src/cli_config.rs @@ -1,9 +1,14 @@ +#[cfg(feature = "server")] +use std::path::Path; + #[allow(unused_imports)] pub(crate) use fabro_config::cli::*; use fabro_config::ConfigLayer; use fabro_config::FabroSettings; +use crate::args::GlobalArgs; + #[cfg(feature = "server")] use tracing::debug; @@ -11,9 +16,19 @@ pub(crate) fn load_cli_settings() -> anyhow::Result { ConfigLayer::cli()?.resolve() } +pub(crate) fn load_cli_settings_with_globals( + globals: &GlobalArgs, +) -> anyhow::Result { + let mut layer = ConfigLayer::cli()?; + if let Some(dir) = &globals.storage_dir { + layer.storage_dir = Some(dir.clone()); + } + layer.resolve() +} + #[cfg(feature = "server")] #[derive(Debug, PartialEq)] -pub struct ResolvedMode { +pub(crate) struct ResolvedMode { pub mode: ExecutionMode, pub server_base_url: String, pub tls: Option, @@ -23,14 +38,18 @@ pub struct ResolvedMode { const DEFAULT_SERVER_URL: &str = "http://localhost:3000"; #[cfg(feature = "server")] -pub fn resolve_mode( - cli_mode: Option, +pub(crate) fn resolve_mode( + cli_storage_dir: Option<&Path>, cli_server_url: Option<&str>, settings: &FabroSettings, ) -> ResolvedMode { - let mode = cli_mode - .or_else(|| settings.mode.clone()) - .unwrap_or_default(); + let mode = if cli_server_url.is_some() { + ExecutionMode::Server + } else if cli_storage_dir.is_some() { + ExecutionMode::Standalone + } else { + settings.mode.clone().unwrap_or_default() + }; let server_defaults = settings.server.as_ref(); @@ -51,7 +70,9 @@ pub fn resolve_mode( } #[cfg(feature = "server")] -pub fn build_server_client(tls: Option<&ClientTlsSettings>) -> anyhow::Result { +pub(crate) fn build_server_client( + tls: Option<&ClientTlsSettings>, +) -> anyhow::Result { let Some(tls) = tls else { return Ok(reqwest::Client::new()); }; @@ -82,7 +103,7 @@ pub fn build_server_client(tls: Option<&ClientTlsSettings>) -> anyhow::Result Result<()> { - let cli_settings = load_cli_settings()?; +pub(super) fn cp_command(args: &AssetCpArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); let (run_id, asset_path) = parse_source(&args.source); let run = resolve_run(&base, run_id)?; diff --git a/lib/crates/fabro-cli/src/commands/asset/list.rs b/lib/crates/fabro-cli/src/commands/asset/list.rs index f719ad14e..cbffa7b93 100644 --- a/lib/crates/fabro-cli/src/commands/asset/list.rs +++ b/lib/crates/fabro-cli/src/commands/asset/list.rs @@ -4,12 +4,12 @@ use fabro_store::RuntimeState; use fabro_workflows::assets::scan_assets; use fabro_workflows::run_lookup::{resolve_run, runs_base}; -use crate::args::AssetListArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{AssetListArgs, GlobalArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; -pub(super) fn list_command(args: &AssetListArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(super) fn list_command(args: &AssetListArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); let run = resolve_run(&base, &args.run_id)?; let runtime_state = RuntimeState::new(&run.path); diff --git a/lib/crates/fabro-cli/src/commands/asset/mod.rs b/lib/crates/fabro-cli/src/commands/asset/mod.rs index 8ce5b3c79..aae4f1101 100644 --- a/lib/crates/fabro-cli/src/commands/asset/mod.rs +++ b/lib/crates/fabro-cli/src/commands/asset/mod.rs @@ -3,11 +3,11 @@ mod list; use anyhow::Result; -use crate::args::{AssetCommand, AssetNamespace}; +use crate::args::{AssetCommand, AssetNamespace, GlobalArgs}; -pub(crate) fn dispatch(ns: AssetNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: AssetNamespace, globals: &GlobalArgs) -> Result<()> { match ns.command { - AssetCommand::List(args) => list::list_command(&args), - AssetCommand::Cp(args) => cp::cp_command(&args), + AssetCommand::List(args) => list::list_command(&args, globals), + AssetCommand::Cp(args) => cp::cp_command(&args, globals), } } diff --git a/lib/crates/fabro-cli/src/commands/config/mod.rs b/lib/crates/fabro-cli/src/commands/config/mod.rs index e02d582a5..2fcaa559e 100644 --- a/lib/crates/fabro-cli/src/commands/config/mod.rs +++ b/lib/crates/fabro-cli/src/commands/config/mod.rs @@ -1,27 +1,31 @@ use std::io::Write; use std::path::Path; -use crate::args::{ConfigCommand, ConfigNamespace, ConfigShowArgs}; +use crate::args::{ConfigCommand, ConfigNamespace, ConfigShowArgs, GlobalArgs}; use fabro_config::{ConfigLayer, FabroSettings}; -pub(crate) fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> { +pub(crate) fn dispatch(ns: ConfigNamespace, globals: &GlobalArgs) -> anyhow::Result<()> { match ns.command { - ConfigCommand::Show(args) => show_command(&args), + ConfigCommand::Show(args) => show_command(&args, globals), } } -fn merged_config(workflow: Option<&Path>) -> anyhow::Result { +fn merged_config(workflow: Option<&Path>, globals: &GlobalArgs) -> anyhow::Result { let cwd = std::env::current_dir()?; let base = match workflow { Some(path) => ConfigLayer::for_workflow(path, &cwd)?, None => ConfigLayer::project(&cwd)?, }; + let mut cli = ConfigLayer::cli()?; + if let Some(dir) = &globals.storage_dir { + cli.storage_dir = Some(dir.clone()); + } - base.combine(ConfigLayer::cli()?).resolve() + base.combine(cli).resolve() } -pub(crate) fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> { - let config = merged_config(args.workflow.as_deref())?; +pub(crate) fn show_command(args: &ConfigShowArgs, globals: &GlobalArgs) -> anyhow::Result<()> { + let config = merged_config(args.workflow.as_deref(), globals)?; let mut yaml = serde_yaml::to_string(&config)?; if !yaml.ends_with('\n') { yaml.push('\n'); diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index c19e6d012..1551e0f18 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -9,7 +9,7 @@ use crate::args::GlobalArgs; use crate::cli_config; pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = cli_config::load_cli_settings()?; + let cli_settings = cli_config::load_cli_settings_with_globals(globals)?; #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = crate::sleep_inhibitor::guard(cli_settings.prevent_idle_sleep_enabled()); let exec_defaults = cli_settings.exec.as_ref(); @@ -21,7 +21,7 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result ); #[cfg(feature = "server")] let resolved = cli_config::resolve_mode( - globals.mode.clone(), + globals.storage_dir.as_deref(), globals.server_url.as_deref(), &cli_settings, ); diff --git a/lib/crates/fabro-cli/src/commands/llm/chat.rs b/lib/crates/fabro-cli/src/commands/llm/chat.rs index e71f30310..7b56dace3 100644 --- a/lib/crates/fabro-cli/src/commands/llm/chat.rs +++ b/lib/crates/fabro-cli/src/commands/llm/chat.rs @@ -19,7 +19,7 @@ pub(super) async fn execute( #[cfg(feature = "server")] { let resolved = crate::cli_config::resolve_mode( - globals.mode.clone(), + globals.storage_dir.as_deref(), globals.server_url.as_deref(), cli_settings, ); diff --git a/lib/crates/fabro-cli/src/commands/llm/mod.rs b/lib/crates/fabro-cli/src/commands/llm/mod.rs index 68f269cf4..64ec710a2 100644 --- a/lib/crates/fabro-cli/src/commands/llm/mod.rs +++ b/lib/crates/fabro-cli/src/commands/llm/mod.rs @@ -4,10 +4,10 @@ mod prompt; use anyhow::Result; use crate::args::{GlobalArgs, LlmCommand, LlmNamespace}; -use crate::cli_config::load_cli_settings; +use crate::cli_config::load_cli_settings_with_globals; pub(crate) async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; match ns.command { LlmCommand::Prompt(args) => prompt::execute(args, &cli_settings, globals).await, diff --git a/lib/crates/fabro-cli/src/commands/llm/prompt.rs b/lib/crates/fabro-cli/src/commands/llm/prompt.rs index 6d08fde35..c8dd636b8 100644 --- a/lib/crates/fabro-cli/src/commands/llm/prompt.rs +++ b/lib/crates/fabro-cli/src/commands/llm/prompt.rs @@ -19,7 +19,7 @@ pub(super) async fn execute( #[cfg(feature = "server")] { let resolved = crate::cli_config::resolve_mode( - globals.mode.clone(), + globals.storage_dir.as_deref(), globals.server_url.as_deref(), cli_settings, ); diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 756a57224..20e2a60ae 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -11,9 +11,9 @@ pub(crate) async fn execute(command: Option, globals: &GlobalArgs let server = { #[cfg(feature = "server")] { - let cli_settings = cli_config::load_cli_settings()?; + let cli_settings = cli_config::load_cli_settings_with_globals(globals)?; let resolved = cli_config::resolve_mode( - globals.mode.clone(), + globals.storage_dir.as_deref(), globals.server_url.as_deref(), &cli_settings, ); diff --git a/lib/crates/fabro-cli/src/commands/pr/close.rs b/lib/crates/fabro-cli/src/commands/pr/close.rs index 32df09ca7..25f16aa92 100644 --- a/lib/crates/fabro-cli/src/commands/pr/close.rs +++ b/lib/crates/fabro-cli/src/commands/pr/close.rs @@ -5,14 +5,15 @@ use fabro_config::FabroSettingsExt; use fabro_workflows::run_lookup::runs_base; use tracing::info; -use crate::args::PrCloseArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrCloseArgs}; +use crate::cli_config::load_cli_settings_with_globals; pub(super) async fn close_command( args: PrCloseArgs, github_app: Option, + globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); close_from(&base, args, github_app).await } diff --git a/lib/crates/fabro-cli/src/commands/pr/create.rs b/lib/crates/fabro-cli/src/commands/pr/create.rs index 644237e09..1e50e6df1 100644 --- a/lib/crates/fabro-cli/src/commands/pr/create.rs +++ b/lib/crates/fabro-cli/src/commands/pr/create.rs @@ -12,14 +12,16 @@ use fabro_workflows::records::{ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; -use crate::args::PrCreateArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrCreateArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; pub(super) async fn create_command( args: PrCreateArgs, github_app: Option, + globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); create_from(&base, args, github_app).await } @@ -30,10 +32,10 @@ async fn create_from( github_app: Option, ) -> Result<()> { let storage_dir = base.parent().unwrap_or(base); - let store = crate::store::build_store(storage_dir)?; + let store = store::build_store(storage_dir)?; let run = resolve_run_combined(store.as_ref(), base, &args.run_id).await?; let run_dir = run.path.clone(); - let run_store = crate::store::open_run_reader(storage_dir, &run.run_id).await?; + let run_store = store::open_run_reader(storage_dir, &run.run_id).await?; let record = match run_store.as_ref() { Some(run_store) => run_store diff --git a/lib/crates/fabro-cli/src/commands/pr/list.rs b/lib/crates/fabro-cli/src/commands/pr/list.rs index 8028d5647..f56df247e 100644 --- a/lib/crates/fabro-cli/src/commands/pr/list.rs +++ b/lib/crates/fabro-cli/src/commands/pr/list.rs @@ -7,16 +7,18 @@ use fabro_workflows::run_lookup::{runs_base, scan_runs_combined}; use futures::future::join_all; use tracing::info; -use crate::args::PrListArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrListArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; pub(super) async fn list_command( args: PrListArgs, github_app: Option, + globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; list_from(store.as_ref(), &base, args, github_app).await } diff --git a/lib/crates/fabro-cli/src/commands/pr/merge.rs b/lib/crates/fabro-cli/src/commands/pr/merge.rs index c4cbc5e6d..2dfa4ee8f 100644 --- a/lib/crates/fabro-cli/src/commands/pr/merge.rs +++ b/lib/crates/fabro-cli/src/commands/pr/merge.rs @@ -6,14 +6,15 @@ use tracing::info; use fabro_workflows::run_lookup::runs_base; -use crate::args::PrMergeArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrMergeArgs}; +use crate::cli_config::load_cli_settings_with_globals; pub(super) async fn merge_command( args: PrMergeArgs, github_app: Option, + globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); merge_from(&base, args, github_app).await } diff --git a/lib/crates/fabro-cli/src/commands/pr/mod.rs b/lib/crates/fabro-cli/src/commands/pr/mod.rs index 018d9d788..94be1b382 100644 --- a/lib/crates/fabro-cli/src/commands/pr/mod.rs +++ b/lib/crates/fabro-cli/src/commands/pr/mod.rs @@ -11,20 +11,21 @@ use anyhow::{Context, Result}; use fabro_workflows::pull_request::PullRequestRecord; use fabro_workflows::run_lookup::resolve_run_combined; -use crate::args::{PrCommand, PrNamespace}; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrCommand, PrNamespace}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::github::build_github_app_credentials; +use crate::store; -pub(crate) async fn dispatch(ns: PrNamespace) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn dispatch(ns: PrNamespace, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let github_app = build_github_app_credentials(cli_settings.app_id()); match ns.command { - PrCommand::Create(args) => create::create_command(args, github_app).await, - PrCommand::List(args) => list::list_command(args, github_app).await, - PrCommand::View(args) => view::view_command(args, github_app).await, - PrCommand::Merge(args) => merge::merge_command(args, github_app).await, - PrCommand::Close(args) => close::close_command(args, github_app).await, + PrCommand::Create(args) => create::create_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, } } @@ -33,7 +34,7 @@ pub(crate) async fn load_pr_record( run_id: &str, ) -> Result<(PullRequestRecord, PathBuf)> { let storage_dir = base.parent().unwrap_or(base); - let store = crate::store::build_store(storage_dir)?; + let store = store::build_store(storage_dir)?; let run_dir = resolve_run_combined(store.as_ref(), base, run_id) .await? .path; diff --git a/lib/crates/fabro-cli/src/commands/pr/view.rs b/lib/crates/fabro-cli/src/commands/pr/view.rs index f13c00242..9cce8c995 100644 --- a/lib/crates/fabro-cli/src/commands/pr/view.rs +++ b/lib/crates/fabro-cli/src/commands/pr/view.rs @@ -6,14 +6,15 @@ use tracing::info; use fabro_workflows::run_lookup::runs_base; -use crate::args::PrViewArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PrViewArgs}; +use crate::cli_config::load_cli_settings_with_globals; pub(super) async fn view_command( args: PrViewArgs, github_app: Option, + globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); view_from(&base, args, github_app).await } diff --git a/lib/crates/fabro-cli/src/commands/preflight.rs b/lib/crates/fabro-cli/src/commands/preflight.rs index f1be24fef..fa27bac3d 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -16,13 +16,17 @@ use fabro_util::terminal::Styles; use fabro_workflows::git::{GitSyncStatus, sync_status}; use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate}; -use crate::args::PreflightArgs; +use crate::args::{GlobalArgs, PreflightArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::github::build_github_app_credentials; -pub(crate) async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> { +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 = ConfigLayer::cli()?; - let cli_settings: FabroSettings = cli.clone().resolve()?; + let mut cli = ConfigLayer::cli()?; + if let Some(dir) = &globals.storage_dir { + cli.storage_dir = Some(dir.clone()); + } + let cli_settings: FabroSettings = load_cli_settings_with_globals(globals)?; args.verbose = args.verbose || cli_settings.verbose_enabled(); let github_app = build_github_app_credentials(cli_settings.app_id()); diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs index 257d84d35..8187bab17 100644 --- a/lib/crates/fabro-cli/src/commands/run/attach.rs +++ b/lib/crates/fabro-cli/src/commands/run/attach.rs @@ -16,9 +16,10 @@ use fabro_workflows::outcome::StageStatus; use fabro_workflows::records::{Conclusion, ConclusionExt, RunRecord, RunRecordExt}; use fabro_workflows::run_status::{RunStatus, RunStatusRecord, RunStatusRecordExt}; use tokio::signal::ctrl_c; -use tokio::time::sleep; +use tokio::time::{self, sleep}; use super::run_progress; +use crate::store; #[cfg(test)] const ATTACH_STARTUP_GRACE: Duration = Duration::from_millis(200); @@ -44,7 +45,7 @@ pub(crate) async fn attach_run( .map(|record| record.settings.storage_dir()), run_id.or_else(|| run_record.as_ref().map(|record| record.run_id.as_str())), ) { - match crate::store::open_run_reader(&storage_dir, run_id).await { + match store::open_run_reader(&storage_dir, run_id).await { Ok(Some(run_store)) => match run_store.list_events().await { Ok(events) => { let event_lines = events @@ -55,7 +56,7 @@ pub(crate) async fn attach_run( run_dir, run_store.as_ref(), event_lines, - events.last().map(|event| event.seq).unwrap_or(0), + events.last().map_or(0, |event| event.seq), kill_on_detach, styles, engine_child, @@ -158,7 +159,7 @@ async fn attach_run_store( } let mut saw_event = false; - match tokio::time::timeout(Duration::from_millis(100), stream.next()).await { + match time::timeout(Duration::from_millis(100), stream.next()).await { Ok(Some(Ok(event))) => { let line = event_payload_line(&event)?; progress_ui.handle_json_line(&line); @@ -642,27 +643,24 @@ fn determine_exit_code(conclusion_path: &Path, status_record: Option ExitCode { - match run_store.get_conclusion().await { - Ok(Some(conclusion)) => { - let success = matches!( - conclusion.status, - StageStatus::Success | StageStatus::PartialSuccess - ); - if success { - ExitCode::from(0) - } else { - ExitCode::from(1) - } - } - Ok(None) | Err(_) => { - let status_path = run_dir.join("status.json"); - let conclusion_path = run_dir.join("conclusion.json"); - let status_record = match run_store.get_status().await { - Ok(record) => record.or_else(|| read_status_record(&status_path)), - Err(_) => read_status_record(&status_path), - }; - determine_exit_code(&conclusion_path, status_record) + if let Ok(Some(conclusion)) = run_store.get_conclusion().await { + let success = matches!( + conclusion.status, + StageStatus::Success | StageStatus::PartialSuccess + ); + if success { + ExitCode::from(0) + } else { + ExitCode::from(1) } + } else { + let status_path = run_dir.join("status.json"); + let conclusion_path = run_dir.join("conclusion.json"); + let status_record = match run_store.get_status().await { + Ok(record) => record.or_else(|| read_status_record(&status_path)), + Err(_) => read_status_record(&status_path), + }; + determine_exit_code(&conclusion_path, status_record) } } diff --git a/lib/crates/fabro-cli/src/commands/run/command.rs b/lib/crates/fabro-cli/src/commands/run/command.rs index 6ed10e0d4..dfff31f64 100644 --- a/lib/crates/fabro-cli/src/commands/run/command.rs +++ b/lib/crates/fabro-cli/src/commands/run/command.rs @@ -3,16 +3,18 @@ use fabro_config::ConfigLayer; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, RunArgs}; +use crate::cli_config; -pub(crate) async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> { +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 = cli_config::load_cli_settings_with_globals(globals)?; let cli = ConfigLayer::cli()?; - let cli_settings = cli.clone().resolve()?; args.verbose = args.verbose || cli_settings.verbose_enabled(); let quiet = args.detach; let prevent_idle_sleep = cli_settings.prevent_idle_sleep_enabled(); - let (run_id, run_dir) = super::create::create_run(&args, cli, styles, quiet)?; + let (run_id, run_dir) = + super::create::create_run(&args, cli, styles, quiet, globals.storage_dir.clone())?; #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep); diff --git a/lib/crates/fabro-cli/src/commands/run/cp.rs b/lib/crates/fabro-cli/src/commands/run/cp.rs index 2684018e5..31a31a0b5 100644 --- a/lib/crates/fabro-cli/src/commands/run/cp.rs +++ b/lib/crates/fabro-cli/src/commands/run/cp.rs @@ -9,8 +9,8 @@ use fabro_workflows::run_lookup::{resolve_run, runs_base}; use tokio::fs; use tracing::{debug, info}; -use crate::args::CpArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{CpArgs, GlobalArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::split_run_path; enum CopyDirection { @@ -26,9 +26,9 @@ enum CopyDirection { }, } -pub(crate) async fn cp_command(args: CpArgs) -> Result<()> { +pub(crate) async fn cp_command(args: CpArgs, globals: &GlobalArgs) -> Result<()> { let direction = parse_direction(&args.src, &args.dst)?; - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); match direction { diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index b36d1835c..d16338db4 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -16,12 +16,16 @@ pub(crate) fn create_run( cli_defaults: ConfigLayer, styles: &Styles, quiet: bool, + storage_dir_override: Option, ) -> anyhow::Result<(String, PathBuf)> { let workflow_path = args .workflow .as_ref() .ok_or_else(|| anyhow::anyhow!("--workflow is required"))?; - let cli_args_config = ConfigLayer::try_from(args)?; + let mut cli_args_config = ConfigLayer::try_from(args)?; + if let Some(dir) = storage_dir_override { + cli_args_config.storage_dir = Some(dir); + } let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let settings: FabroSettings = cli_args_config .combine(ConfigLayer::for_workflow(workflow_path, &cwd)?) diff --git a/lib/crates/fabro-cli/src/commands/run/detached.rs b/lib/crates/fabro-cli/src/commands/run/detached.rs index 811e5d2d3..95e90640d 100644 --- a/lib/crates/fabro-cli/src/commands/run/detached.rs +++ b/lib/crates/fabro-cli/src/commands/run/detached.rs @@ -14,6 +14,7 @@ use fabro_workflows::records::{RunRecord, RunRecordExt}; use crate::cli_config; use crate::shared; +use crate::store; pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> { let _ = fabro_proctitle::init(); @@ -31,7 +32,7 @@ pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bo fabro_proctitle::set(&format!("fabro: {short_id} {node_id}")); }) as Arc }); - let store = crate::store::build_store(&run_record.settings.storage_dir())?; + let store = store::build_store(&run_record.settings.storage_dir())?; let run_store = open_or_hydrate_run(store.as_ref(), &run_dir).await?; let github_app = shared::github::build_github_app_credentials(cli_settings.app_id()); diff --git a/lib/crates/fabro-cli/src/commands/run/diff.rs b/lib/crates/fabro-cli/src/commands/run/diff.rs index 5deeef2ef..f8b9787b0 100644 --- a/lib/crates/fabro-cli/src/commands/run/diff.rs +++ b/lib/crates/fabro-cli/src/commands/run/diff.rs @@ -10,16 +10,17 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use fabro_workflows::sandbox_git::GIT_REMOTE; use tracing::{debug, info}; -use crate::args::DiffArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{DiffArgs, GlobalArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; -pub(crate) async fn run(args: DiffArgs) -> Result<()> { +pub(crate) async fn run(args: DiffArgs, globals: &GlobalArgs) -> Result<()> { info!(run_id = %args.run, "Showing diff"); - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; - let run_store = crate::store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await?; + let run_store = store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await?; let patch = resolve_diff(&run.path, run_store.as_deref(), &args).await?; diff --git a/lib/crates/fabro-cli/src/commands/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index 2c738427f..24eb0a6cd 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -8,13 +8,13 @@ use fabro_workflows::operations::{ }; use git2::Repository; -use crate::args::ForkArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{ForkArgs, GlobalArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::store::{build_store, open_run_reader}; -pub(crate) async fn run(args: &ForkArgs, styles: &Styles) -> Result<()> { +pub(crate) async fn run(args: &ForkArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let durable_store = build_store(&cli_settings.storage_dir())?; let run_id = find_run_id_by_prefix_or_store(&repo, durable_store.as_ref(), &args.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 f94673bdd..32388627e 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -9,15 +9,17 @@ use fabro_store::RunStore; use fabro_util::terminal::Styles; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use futures::StreamExt; +use tokio::time; use tracing::{debug, info, warn}; -use crate::args::LogsArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, LogsArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; -pub(crate) async fn run(args: &LogsArgs, styles: &Styles) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn run(args: &LogsArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; info!(run_id = %run.run_id, "Showing logs"); @@ -27,12 +29,12 @@ pub(crate) async fn run(args: &LogsArgs, styles: &Styles) -> Result<()> { None => None, }; - let run_store = crate::store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await?; + let run_store = store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await?; let progress_path = run.path.join("progress.jsonl"); - let (all_lines, last_seq, use_store_follow) = match run_store.as_ref() { - Some(run_store) => match run_store.list_events().await { + let (all_lines, last_seq, use_store_follow) = if let Some(run_store) = run_store.as_ref() { + match run_store.list_events().await { Ok(events) => { - let last_seq = events.last().map(|event| event.seq).unwrap_or(0); + let last_seq = events.last().map_or(0, |event| event.seq); let lines = events .iter() .map(event_payload_line) @@ -50,13 +52,12 @@ pub(crate) async fn run(args: &LogsArgs, styles: &Styles) -> Result<()> { ); (read_lines(&progress_path)?, 0, false) } - }, - None => { - if !progress_path.exists() { - bail!("No progress.jsonl found for run '{}'", run.run_id); - } - (read_lines(&progress_path)?, 0, false) } + } else { + if !progress_path.exists() { + bail!("No progress.jsonl found for run '{}'", run.run_id); + } + (read_lines(&progress_path)?, 0, false) }; let filtered = apply_filters(&all_lines, since_cutoff.as_ref(), args.tail); @@ -252,7 +253,7 @@ async fn follow_store_logs( let mut out = stdout.lock(); loop { - match tokio::time::timeout(Duration::from_millis(200), stream.next()).await { + match time::timeout(Duration::from_millis(200), stream.next()).await { Ok(Some(Ok(event))) => { let line = event_payload_line(&event)?; if pretty { diff --git a/lib/crates/fabro-cli/src/commands/run/mod.rs b/lib/crates/fabro-cli/src/commands/run/mod.rs index b3add82fb..e1dec0e77 100644 --- a/lib/crates/fabro-cli/src/commands/run/mod.rs +++ b/lib/crates/fabro-cli/src/commands/run/mod.rs @@ -5,7 +5,8 @@ use fabro_util::terminal::Styles; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use crate::args::{GlobalArgs, RunCommands}; -use crate::cli_config::load_cli_settings; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; pub(crate) mod attach; pub(crate) mod command; @@ -36,14 +37,15 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( RunCommands::Create(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); let cli = ConfigLayer::cli()?; - let (run_id, _run_dir) = create::create_run(&args, cli, styles, true)?; + let (run_id, _run_dir) = + create::create_run(&args, cli, styles, true, globals.storage_dir.clone())?; println!("{run_id}"); Ok(()) } RunCommands::Start { run } => { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run_info = resolve_run_combined(store.as_ref(), &base, &run).await?; let child = start::start_run(&run_info.path, false)?; eprintln!("Started engine process (PID {})", child.id()); @@ -51,9 +53,9 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( } RunCommands::Attach { run } => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run_info = resolve_run_combined(store.as_ref(), &base, &run).await?; let exit_code = attach::attach_run(&run_info.path, Some(&run_info.run_id), false, styles, None) @@ -68,34 +70,34 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( launcher_path, resume, } => detached::execute(run_dir, launcher_path, resume).await, - RunCommands::Cp(args) => cp::cp_command(args).await, - RunCommands::Preview(args) => preview::run(args).await, - RunCommands::Ssh(args) => ssh::run(args).await, - RunCommands::Diff(args) => diff::run(args).await, + RunCommands::Cp(args) => cp::cp_command(args, globals).await, + RunCommands::Preview(args) => preview::run(args, globals).await, + RunCommands::Ssh(args) => ssh::run(args, globals).await, + RunCommands::Diff(args) => diff::run(args, globals).await, RunCommands::Logs(args) => { let styles = Styles::detect_stdout(); - logs::run(&args, &styles).await + logs::run(&args, &styles, globals).await } RunCommands::Resume(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; crate::sleep_inhibitor::guard(cli_settings.prevent_idle_sleep_enabled()) }; - resume::resume_command(args, styles).await + resume::resume_command(args, styles, globals).await } RunCommands::Rewind(args) => { let styles = Styles::detect_stderr(); - rewind::run(&args, &styles).await + rewind::run(&args, &styles, globals).await } RunCommands::Fork(args) => { let styles = Styles::detect_stderr(); - fork::run(&args, &styles).await + fork::run(&args, &styles, globals).await } RunCommands::Wait(args) => { let styles = Styles::detect_stderr(); - wait::run(&args, &styles).await + wait::run(&args, &styles, globals).await } } } diff --git a/lib/crates/fabro-cli/src/commands/run/overrides.rs b/lib/crates/fabro-cli/src/commands/run/overrides.rs index 3d9b63bad..14751ff71 100644 --- a/lib/crates/fabro-cli/src/commands/run/overrides.rs +++ b/lib/crates/fabro-cli/src/commands/run/overrides.rs @@ -54,7 +54,6 @@ impl TryFrom<&RunArgs> for ConfigLayer { dry_run: sparse_flag(args.dry_run), auto_approve: sparse_flag(args.auto_approve), no_retro: sparse_flag(args.no_retro), - storage_dir: args.storage_dir.clone(), labels: parse_labels(&args.label), ..Default::default() }) diff --git a/lib/crates/fabro-cli/src/commands/run/preview.rs b/lib/crates/fabro-cli/src/commands/run/preview.rs index 3d2daf07b..7567d3eaa 100644 --- a/lib/crates/fabro-cli/src/commands/run/preview.rs +++ b/lib/crates/fabro-cli/src/commands/run/preview.rs @@ -5,19 +5,18 @@ use fabro_sandbox::daytona::DaytonaSandbox; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; -use crate::args::PreviewArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, PreviewArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::validate_daytona_provider; +use crate::store; -pub(crate) async fn run(args: PreviewArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; let sandbox_json = run.path.join("sandbox.json"); - let record = match crate::store::open_run_reader(&cli_settings.storage_dir(), &run.run_id) - .await? - { + let record = match store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await? { Some(run_store) => run_store .get_sandbox() .await diff --git a/lib/crates/fabro-cli/src/commands/run/resume.rs b/lib/crates/fabro-cli/src/commands/run/resume.rs index 8f9c9df95..282fe3d87 100644 --- a/lib/crates/fabro-cli/src/commands/run/resume.rs +++ b/lib/crates/fabro-cli/src/commands/run/resume.rs @@ -4,8 +4,9 @@ use fabro_util::terminal::Styles; use fabro_workflows::records::{RunRecord, RunRecordExt}; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; -use crate::args::ResumeArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, ResumeArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; /// Resume an interrupted workflow run. /// @@ -15,10 +16,11 @@ use crate::cli_config::load_cli_settings; pub(crate) async fn resume_command( args: ResumeArgs, styles: &'static Styles, + globals: &GlobalArgs, ) -> anyhow::Result<()> { - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; let run_dir = run.path; diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index 60bf57157..eefce621b 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -11,14 +11,14 @@ use fabro_workflows::operations::{ }; use git2::Repository; -use crate::args::RewindArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, RewindArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::color_if; use crate::store::{build_store, open_run_reader}; -pub(crate) async fn run(args: &RewindArgs, styles: &Styles) -> Result<()> { +pub(crate) async fn run(args: &RewindArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; - let cli_settings = load_cli_settings()?; + let cli_settings = load_cli_settings_with_globals(globals)?; let durable_store = build_store(&cli_settings.storage_dir())?; let run_id = find_run_id_by_prefix_or_store(&repo, durable_store.as_ref(), &args.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 eb2f32f2c..b016e19e8 100644 --- a/lib/crates/fabro-cli/src/commands/run/ssh.rs +++ b/lib/crates/fabro-cli/src/commands/run/ssh.rs @@ -5,19 +5,18 @@ use fabro_sandbox::daytona::DaytonaSandbox; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; -use crate::args::SshArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, SshArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::validate_daytona_provider; +use crate::store; -pub(crate) async fn run(args: SshArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; let sandbox_json = run.path.join("sandbox.json"); - let record = match crate::store::open_run_reader(&cli_settings.storage_dir(), &run.run_id) - .await? - { + let record = match store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await? { Some(run_store) => run_store .get_sandbox() .await diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index 9741c1002..29a243c56 100644 --- a/lib/crates/fabro-cli/src/commands/run/wait.rs +++ b/lib/crates/fabro-cli/src/commands/run/wait.rs @@ -8,20 +8,20 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use fabro_workflows::run_status::{RunStatus, RunStatusRecord, RunStatusRecordExt}; use tracing::info; -use crate::args::WaitArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, WaitArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_duration_ms; +use crate::store; -pub(crate) async fn run(args: &WaitArgs, styles: &Styles) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn run(args: &WaitArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run_info = resolve_run_combined(store.as_ref(), &base, &args.run).await?; info!(run_id = %run_info.run_id, "Waiting for run to complete"); - let run_store = - crate::store::open_run_reader(&cli_settings.storage_dir(), &run_info.run_id).await?; + let run_store = store::open_run_reader(&cli_settings.storage_dir(), &run_info.run_id).await?; let status_path = run_info.path.join("status.json"); let deadline = args .timeout diff --git a/lib/crates/fabro-cli/src/commands/runs/inspect.rs b/lib/crates/fabro-cli/src/commands/runs/inspect.rs index db4fe26c1..6482e6b94 100644 --- a/lib/crates/fabro-cli/src/commands/runs/inspect.rs +++ b/lib/crates/fabro-cli/src/commands/runs/inspect.rs @@ -10,8 +10,9 @@ use fabro_workflows::records::{Checkpoint, Conclusion, RunRecord, StartRecord}; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use fabro_workflows::run_status::RunStatus; -use crate::args::InspectArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, InspectArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; #[derive(Debug, Serialize)] pub(crate) struct InspectOutput { @@ -25,18 +26,17 @@ pub(crate) struct InspectOutput { pub sandbox: Option, } -pub(crate) async fn run(args: &InspectArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn run(args: &InspectArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; - let output = - match crate::store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await? { - Some(run_store) => { - inspect_run_store(&run.run_id, &run.path, run.status, run_store.as_ref()).await - } - None => inspect_run_dir(&run.run_id, &run.path, run.status), - }; + let output = match store::open_run_reader(&cli_settings.storage_dir(), &run.run_id).await? { + Some(run_store) => { + inspect_run_store(&run.run_id, &run.path, run.status, run_store.as_ref()).await + } + None => inspect_run_dir(&run.run_id, &run.path, run.status), + }; let json = serde_json::to_string_pretty(&[output])?; println!("{json}"); Ok(()) @@ -55,8 +55,7 @@ async fn inspect_run_store( status: snapshot .status .as_ref() - .map(|record| record.status) - .unwrap_or(status), + .map_or(status, |record| record.status), run_record: serde_json::to_value(snapshot.run).ok(), start_record: snapshot .start diff --git a/lib/crates/fabro-cli/src/commands/runs/list.rs b/lib/crates/fabro-cli/src/commands/runs/list.rs index 811eee809..40d1a3d7e 100644 --- a/lib/crates/fabro-cli/src/commands/runs/list.rs +++ b/lib/crates/fabro-cli/src/commands/runs/list.rs @@ -11,16 +11,21 @@ use fabro_util::text::strip_goal_decoration; use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_runs_combined}; use fabro_workflows::run_status::RunStatus; -use crate::args::RunsListArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, RunsListArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::{color_if, format_duration_ms, tilde_path}; +use crate::store; use super::short_run_id; -pub(crate) async fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn list_command( + args: &RunsListArgs, + styles: &Styles, + globals: &GlobalArgs, +) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; let runs = scan_runs_combined(store.as_ref(), &base).await?; let label_filters = parse_label_filters(&args.filter.label); let filtered = filter_runs( diff --git a/lib/crates/fabro-cli/src/commands/runs/mod.rs b/lib/crates/fabro-cli/src/commands/runs/mod.rs index ed3354e08..a05f1d76b 100644 --- a/lib/crates/fabro-cli/src/commands/runs/mod.rs +++ b/lib/crates/fabro-cli/src/commands/runs/mod.rs @@ -1,20 +1,20 @@ use anyhow::Result; use fabro_util::terminal::Styles; -use crate::args::RunsCommands; +use crate::args::{GlobalArgs, RunsCommands}; pub(crate) mod inspect; pub(crate) mod list; pub(crate) mod rm; -pub(crate) async fn dispatch(cmd: RunsCommands) -> Result<()> { +pub(crate) async fn dispatch(cmd: RunsCommands, globals: &GlobalArgs) -> Result<()> { match cmd { RunsCommands::Ps(args) => { let styles = Styles::detect_stdout(); - list::list_command(&args, &styles).await + list::list_command(&args, &styles, globals).await } - RunsCommands::Rm(args) => rm::remove_command(&args).await, - RunsCommands::Inspect(args) => inspect::run(&args).await, + RunsCommands::Rm(args) => rm::remove_command(&args, globals).await, + RunsCommands::Inspect(args) => inspect::run(&args, globals).await, } } diff --git a/lib/crates/fabro-cli/src/commands/runs/rm.rs b/lib/crates/fabro-cli/src/commands/runs/rm.rs index 469c08ada..2f5b5e6a8 100644 --- a/lib/crates/fabro-cli/src/commands/runs/rm.rs +++ b/lib/crates/fabro-cli/src/commands/runs/rm.rs @@ -8,17 +8,18 @@ use tracing::warn; use fabro_sandbox::reconnect::reconnect as reconnect_sandbox; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; -use fabro_workflows::run_status::{RunStatus, write_run_status}; +use fabro_workflows::run_status::{RunStatus, RunStatusRecord, write_run_status}; -use crate::args::RunsRemoveArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, RunsRemoveArgs}; +use crate::cli_config::load_cli_settings_with_globals; +use crate::store; use super::short_run_id; -pub(crate) async fn remove_command(args: &RunsRemoveArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn remove_command(args: &RunsRemoveArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; remove_from(args, store.as_ref(), &base).await } @@ -48,10 +49,7 @@ async fn remove_from(args: &RunsRemoveArgs, store: &dyn Store, base: &Path) -> R write_run_status(&run.path, RunStatus::Removing, None); if let Ok(Some(run_store)) = store.open_run_reader(&run.run_id).await { if let Err(err) = run_store - .put_status(&fabro_workflows::run_status::RunStatusRecord::new( - RunStatus::Removing, - None, - )) + .put_status(&RunStatusRecord::new(RunStatus::Removing, None)) .await { warn!( diff --git a/lib/crates/fabro-cli/src/commands/store/dump.rs b/lib/crates/fabro-cli/src/commands/store/dump.rs index b9cedbc07..eaa2958c1 100644 --- a/lib/crates/fabro-cli/src/commands/store/dump.rs +++ b/lib/crates/fabro-cli/src/commands/store/dump.rs @@ -7,12 +7,12 @@ use fabro_store::{NodeVisitRef, RunSnapshot, RunStore}; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use serde::Serialize; -use crate::args::StoreDumpArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, StoreDumpArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::store; -pub(crate) async fn dump_command(args: &StoreDumpArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(crate) async fn dump_command(args: &StoreDumpArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); let store = store::build_store(&cli_settings.storage_dir())?; let run = resolve_run_combined(store.as_ref(), &base, &args.run).await?; diff --git a/lib/crates/fabro-cli/src/commands/store/mod.rs b/lib/crates/fabro-cli/src/commands/store/mod.rs index 44ed9963b..1012ac373 100644 --- a/lib/crates/fabro-cli/src/commands/store/mod.rs +++ b/lib/crates/fabro-cli/src/commands/store/mod.rs @@ -2,10 +2,10 @@ mod dump; use anyhow::Result; -use crate::args::{StoreCommand, StoreNamespace}; +use crate::args::{GlobalArgs, StoreCommand, StoreNamespace}; -pub(crate) async fn dispatch(ns: StoreNamespace) -> Result<()> { +pub(crate) async fn dispatch(ns: StoreNamespace, globals: &GlobalArgs) -> Result<()> { match ns.command { - StoreCommand::Dump(args) => dump::dump_command(&args).await, + StoreCommand::Dump(args) => dump::dump_command(&args, globals).await, } } diff --git a/lib/crates/fabro-cli/src/commands/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index 1d3c379b4..5d4d891d9 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -9,16 +9,17 @@ use fabro_config::FabroSettingsExt; use fabro_workflows::run_lookup::{logs_base, runs_base, scan_runs_combined}; use fabro_workflows::run_status::RunStatus; -use crate::args::DfArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{DfArgs, GlobalArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; +use crate::store; -pub(super) async fn df_command(args: &DfArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(super) async fn df_command(args: &DfArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let data_dir = cli_settings.storage_dir(); let runs_base_dir = runs_base(&data_dir); let logs_base_dir = logs_base(&data_dir); - let store = crate::store::build_store(&data_dir)?; + let store = store::build_store(&data_dir)?; df_from( args, store.as_ref(), diff --git a/lib/crates/fabro-cli/src/commands/system/mod.rs b/lib/crates/fabro-cli/src/commands/system/mod.rs index d41d33057..09062e017 100644 --- a/lib/crates/fabro-cli/src/commands/system/mod.rs +++ b/lib/crates/fabro-cli/src/commands/system/mod.rs @@ -3,13 +3,13 @@ mod prune; use anyhow::Result; -use crate::args::{SystemCommand, SystemNamespace}; +use crate::args::{GlobalArgs, SystemCommand, SystemNamespace}; pub(crate) use prune::parse_duration; -pub(crate) async fn dispatch(ns: SystemNamespace) -> Result<()> { +pub(crate) async fn dispatch(ns: SystemNamespace, globals: &GlobalArgs) -> Result<()> { match ns.command { - SystemCommand::Prune(args) => prune::prune_command(&args).await, - SystemCommand::Df(args) => df::df_command(&args).await, + SystemCommand::Prune(args) => prune::prune_command(&args, globals).await, + SystemCommand::Df(args) => df::df_command(&args, globals).await, } } diff --git a/lib/crates/fabro-cli/src/commands/system/prune.rs b/lib/crates/fabro-cli/src/commands/system/prune.rs index 6d702a2ba..809056715 100644 --- a/lib/crates/fabro-cli/src/commands/system/prune.rs +++ b/lib/crates/fabro-cli/src/commands/system/prune.rs @@ -8,14 +8,15 @@ use tracing::{debug, info}; use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_runs_combined}; -use crate::args::RunsPruneArgs; -use crate::cli_config::load_cli_settings; +use crate::args::{GlobalArgs, RunsPruneArgs}; +use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; +use crate::store; -pub(super) async fn prune_command(args: &RunsPruneArgs) -> Result<()> { - let cli_settings = load_cli_settings()?; +pub(super) async fn prune_command(args: &RunsPruneArgs, globals: &GlobalArgs) -> Result<()> { + let cli_settings = load_cli_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); - let store = crate::store::build_store(&cli_settings.storage_dir())?; + let store = store::build_store(&cli_settings.storage_dir())?; prune_from(args, store.as_ref(), &base).await } diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 6dc0aee49..e315a3e6b 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -166,7 +166,7 @@ async fn main_inner() -> (String, Result<()>) { Commands::Llm(ns) => commands::llm::dispatch(ns, &globals).await?, Commands::Exec(args) => commands::exec::execute(args, &globals).await?, Commands::RunCmd(cmd) => commands::run::dispatch(cmd, &globals).await?, - Commands::Preflight(args) => commands::preflight::execute(args).await?, + Commands::Preflight(args) => commands::preflight::execute(args, &globals).await?, Commands::Validate(args) => { let styles = Styles::detect_stderr(); commands::validate::run(&args, &styles)?; @@ -178,14 +178,15 @@ async fn main_inner() -> (String, Result<()>) { Commands::Parse(args) => { commands::parse::run(&args)?; } - Commands::Asset(ns) => commands::asset::dispatch(ns)?, - Commands::Store(ns) => commands::store::dispatch(ns).await?, - Commands::RunsCmd(cmd) => commands::runs::dispatch(cmd).await?, + Commands::Asset(ns) => commands::asset::dispatch(ns, &globals)?, + Commands::Store(ns) => commands::store::dispatch(ns, &globals).await?, + Commands::RunsCmd(cmd) => commands::runs::dispatch(cmd, &globals).await?, Commands::Model { command } => commands::model::execute(command, &globals).await?, #[cfg(feature = "server")] Commands::Serve(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - fabro_server::serve::serve_command(args, styles).await?; + fabro_server::serve::serve_command(args, styles, globals.storage_dir.clone()) + .await?; } Commands::Doctor { verbose, dry_run } => { let cli_settings = cli_config::load_cli_settings()?; @@ -207,16 +208,16 @@ async fn main_inner() -> (String, Result<()>) { Commands::Install { web_url } => { commands::install::run_install(&web_url).await?; } - Commands::Pr(ns) => commands::pr::dispatch(ns).await?, + Commands::Pr(ns) => commands::pr::dispatch(ns, &globals).await?, Commands::Secret(ns) => commands::secret::dispatch(ns)?, - Commands::Config(ns) => commands::config::dispatch(ns)?, + Commands::Config(ns) => commands::config::dispatch(ns, &globals)?, Commands::Workflow(ns) => commands::workflow::dispatch(ns)?, Commands::Skill(ns) => commands::skill::dispatch(ns)?, Commands::Upgrade(args) => { commands::upgrade::run_upgrade(args).await?; } Commands::Provider(ns) => commands::provider::dispatch(ns).await?, - Commands::System(ns) => commands::system::dispatch(ns).await?, + Commands::System(ns) => commands::system::dispatch(ns, &globals).await?, Commands::SendAnalytics { path } => { let result = sender::upload(&path).await; let _ = std::fs::remove_file(&path); @@ -306,6 +307,46 @@ mod tests { } } + #[test] + fn parse_global_storage_dir_after_subcommand() { + let cli = Cli::try_parse_from([ + "fabro", + "run", + "test/simple.fabro", + "--storage-dir", + "/tmp/fabro", + ]) + .expect("should parse"); + assert_eq!( + cli.globals.storage_dir.as_deref(), + Some(std::path::Path::new("/tmp/fabro")) + ); + match *cli.command { + Commands::RunCmd(RunCommands::Run(args)) => { + assert_eq!( + args.workflow.as_deref(), + Some(std::path::Path::new("test/simple.fabro")) + ); + } + _ => panic!("unexpected command variant"), + } + } + + #[test] + #[cfg(feature = "server")] + fn parse_server_url_conflicts_with_storage_dir() { + let result = Cli::try_parse_from([ + "fabro", + "--storage-dir", + "/tmp/fabro", + "--server-url", + "http://localhost:3000", + "model", + "list", + ]); + assert!(result.is_err(), "should fail with conflicting global flags"); + } + #[test] fn parse_store_dump_command() { let cli = Cli::try_parse_from(["fabro", "store", "dump", "ABC123", "-o", "./out"]) diff --git a/lib/crates/fabro-cli/tests/snapshots/it__cli__serve_help.snap b/lib/crates/fabro-cli/tests/it/snapshots/it__cli__serve_help.snap similarity index 82% rename from lib/crates/fabro-cli/tests/snapshots/it__cli__serve_help.snap rename to lib/crates/fabro-cli/tests/it/snapshots/it__cli__serve_help.snap index 7e02a992a..e535be0f8 100644 --- a/lib/crates/fabro-cli/tests/snapshots/it__cli__serve_help.snap +++ b/lib/crates/fabro-cli/tests/it/snapshots/it__cli__serve_help.snap @@ -1,6 +1,6 @@ --- source: lib/crates/fabro-cli/tests/it/cli.rs -assertion_line: 349 +assertion_line: 745 expression: stdout --- Start the HTTP API server @@ -16,18 +16,22 @@ Options: Host address to bind to [default: 127.0.0.1] --no-upgrade-check Disable automatic upgrade check - --mode - Execution mode: standalone (in-process) or server (delegate to API) --model Override default LLM model + --quiet + Suppress non-essential output --provider Override default LLM provider - --server-url - Server URL (overrides server.base_url from cli.toml) + --verbose + Enable verbose output --dry-run Execute with simulated LLM backend + --storage-dir + Storage directory (default: ~/.fabro) --sandbox Sandbox for agent tools + --server-url + Server URL (overrides server.base_url from cli.toml) --max-concurrent-runs Maximum number of concurrent run executions --config diff --git a/lib/crates/fabro-server/src/serve.rs b/lib/crates/fabro-server/src/serve.rs index 1e85c5111..b178ba67a 100644 --- a/lib/crates/fabro-server/src/serve.rs +++ b/lib/crates/fabro-server/src/serve.rs @@ -64,7 +64,11 @@ pub struct ServeArgs { /// /// Returns an error if the server fails to bind or encounters a fatal error. #[allow(clippy::print_stderr)] -pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::Result<()> { +pub async fn serve_command( + args: ServeArgs, + styles: &'static Styles, + storage_dir_override: Option, +) -> anyhow::Result<()> { // Resolve dry-run mode (same pattern as run.rs) let dry_run_mode = if args.dry_run { true @@ -91,7 +95,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow:: // Initialize data directory and SQLite database let config_path = args.config; let server_settings = load_server_settings(config_path.as_deref())?; - let data_dir = resolve_storage_dir(&server_settings); + let data_dir = storage_dir_override.unwrap_or_else(|| resolve_storage_dir(&server_settings)); // Shared config for live reloading let shared_settings = Arc::new(RwLock::new(server_settings)); diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index c4e6b1fdd..4609745d8 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -16,7 +16,8 @@ use fabro_llm::types::{ ContentPart, FinishReason, Message as LlmMessage, Request as LlmRequest, Response as LlmResponse, Role, StreamEvent, ToolChoice, ToolDefinition, Usage, }; -use fabro_retro::retro::{Retro, derive_retro}; +use fabro_retro::retro; +use fabro_retro::retro::Retro; use fabro_store::{InMemoryStore, Store}; use fabro_util::redact::redact_jsonl_line; use fabro_workflows::error::FabroError; @@ -820,10 +821,10 @@ async fn execute_run(state: Arc, run_id: String) { Ok(events) => fabro_workflows::extract_stage_durations_from_events(&events), Err(err) => { tracing::warn!(run_id = %run_id, error = %err, "Failed to load run events from store"); - fabro_retro::retro::extract_stage_durations(&run_options.run_dir) + retro::extract_stage_durations(&run_options.run_dir) } }; - let retro = derive_retro( + let retro = retro::derive_retro( &run_id, "workflow", "",