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",
"",