From be6e37fa268df1cbb1db4a2ee2ccbfc0e0af023e Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 29 Mar 2026 21:24:10 -0400 Subject: [PATCH] Rename cli.toml to user.toml --- docs/administration/deploy-server.mdx | 6 +- docs/administration/server-configuration.mdx | 2 +- docs/agents/hooks.mdx | 4 +- docs/agents/mcp.mdx | 2 +- docs/docs.json | 2 +- docs/reference/cli.mdx | 12 +- ...nfiguration.mdx => user-configuration.mdx} | 24 ++-- lib/crates/fabro-agent/src/cli.rs | 4 +- lib/crates/fabro-cli/src/args.rs | 2 +- lib/crates/fabro-cli/src/commands/asset/cp.rs | 4 +- .../fabro-cli/src/commands/asset/list.rs | 4 +- .../fabro-cli/src/commands/config/mod.rs | 4 +- lib/crates/fabro-cli/src/commands/doctor.rs | 79 ++++++++-- lib/crates/fabro-cli/src/commands/exec.rs | 12 +- lib/crates/fabro-cli/src/commands/graph.rs | 2 +- lib/crates/fabro-cli/src/commands/install.rs | 23 +-- lib/crates/fabro-cli/src/commands/llm/chat.rs | 8 +- lib/crates/fabro-cli/src/commands/llm/mod.rs | 4 +- .../fabro-cli/src/commands/llm/prompt.rs | 8 +- lib/crates/fabro-cli/src/commands/model.rs | 12 +- lib/crates/fabro-cli/src/commands/pr/close.rs | 4 +- .../fabro-cli/src/commands/pr/create.rs | 4 +- lib/crates/fabro-cli/src/commands/pr/list.rs | 4 +- lib/crates/fabro-cli/src/commands/pr/merge.rs | 4 +- lib/crates/fabro-cli/src/commands/pr/mod.rs | 4 +- lib/crates/fabro-cli/src/commands/pr/view.rs | 4 +- .../fabro-cli/src/commands/preflight.rs | 6 +- .../fabro-cli/src/commands/repo/init.rs | 4 +- .../fabro-cli/src/commands/run/command.rs | 6 +- lib/crates/fabro-cli/src/commands/run/cp.rs | 4 +- .../fabro-cli/src/commands/run/detached.rs | 4 +- lib/crates/fabro-cli/src/commands/run/diff.rs | 4 +- lib/crates/fabro-cli/src/commands/run/fork.rs | 4 +- lib/crates/fabro-cli/src/commands/run/logs.rs | 4 +- lib/crates/fabro-cli/src/commands/run/mod.rs | 10 +- .../fabro-cli/src/commands/run/preview.rs | 4 +- .../fabro-cli/src/commands/run/resume.rs | 4 +- .../fabro-cli/src/commands/run/rewind.rs | 4 +- lib/crates/fabro-cli/src/commands/run/ssh.rs | 4 +- lib/crates/fabro-cli/src/commands/run/wait.rs | 4 +- .../fabro-cli/src/commands/runs/inspect.rs | 4 +- .../fabro-cli/src/commands/runs/list.rs | 4 +- lib/crates/fabro-cli/src/commands/runs/rm.rs | 4 +- .../fabro-cli/src/commands/store/dump.rs | 4 +- .../fabro-cli/src/commands/system/df.rs | 4 +- .../fabro-cli/src/commands/system/prune.rs | 4 +- lib/crates/fabro-cli/src/commands/validate.rs | 2 +- lib/crates/fabro-cli/src/main.rs | 8 +- .../src/{cli_config.rs => user_config.rs} | 14 +- lib/crates/fabro-cli/tests/it/cli.rs | 82 +++++++++-- .../it/snapshots/it__cli__serve_help.snap | 14 +- lib/crates/fabro-cli/tests/it/trycmd.rs | 3 + lib/crates/fabro-config/src/cli.rs | 77 ---------- lib/crates/fabro-config/src/config.rs | 12 +- lib/crates/fabro-config/src/lib.rs | 2 +- lib/crates/fabro-config/src/user.rs | 135 ++++++++++++++++++ lib/crates/fabro-types/src/settings/mod.rs | 8 +- .../src/settings/{cli.rs => user.rs} | 0 58 files changed, 434 insertions(+), 255 deletions(-) rename docs/reference/{cli-configuration.mdx => user-configuration.mdx} (91%) rename lib/crates/fabro-cli/src/{cli_config.rs => user_config.rs} (94%) delete mode 100644 lib/crates/fabro-config/src/cli.rs create mode 100644 lib/crates/fabro-config/src/user.rs rename lib/crates/fabro-types/src/settings/{cli.rs => user.rs} (100%) diff --git a/docs/administration/deploy-server.mdx b/docs/administration/deploy-server.mdx index dce14f8bd..5818d0eca 100644 --- a/docs/administration/deploy-server.mdx +++ b/docs/administration/deploy-server.mdx @@ -110,9 +110,9 @@ Send the `X-Fabro-Demo: 1` header on any API request to get static mock data wit ## Pointing the CLI at a server -The CLI can delegate commands to a running Fabro server instead of executing locally. Set `mode = "server"` in `~/.fabro/cli.toml`: +The CLI can delegate commands to a running Fabro server instead of executing locally. Set `mode = "server"` in `~/.fabro/user.toml`: -```toml title="cli.toml" +```toml title="user.toml" mode = "server" [server] @@ -125,7 +125,7 @@ Or use the `--server-url` flag: fabro --server-url https://fabro.example.com:3000 model list ``` -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. +This applies to commands like `fabro model list`, `fabro llm chat`, and `fabro exec`. See [User Configuration](/reference/user-configuration#mode) for the full options including mTLS setup. ## Next steps diff --git a/docs/administration/server-configuration.mdx b/docs/administration/server-configuration.mdx index 8d58899e6..31736f164 100644 --- a/docs/administration/server-configuration.mdx +++ b/docs/administration/server-configuration.mdx @@ -119,7 +119,7 @@ Customize the git author identity used for checkpoint commits. When not set, def | `name` | Git author name | `"fabro"` | | `email` | Git author email | `"fabro@local"` | -The CLI can also set `[git.author]` in `cli.toml` to override the server default. +The CLI can also set `[git.author]` in `user.toml` to override the server default. ### `[git.webhooks]` section diff --git a/docs/agents/hooks.mdx b/docs/agents/hooks.mdx index 82a7a7308..b415da6a3 100644 --- a/docs/agents/hooks.mdx +++ b/docs/agents/hooks.mdx @@ -107,7 +107,7 @@ Hooks are defined as `[[hooks]]` entries in any of these TOML config files: - **`fabro.toml`** — project-level hooks, apply to all workflows in the project - **`workflow.toml`** — per-workflow hooks -- **`~/.fabro/cli.toml`** or **`~/.fabro/server.toml`** — global defaults for all runs +- **`~/.fabro/user.toml`** or **`~/.fabro/server.toml`** — global defaults for all runs See [Merging hook configs](#merging-hook-configs) for how these layers combine. @@ -344,7 +344,7 @@ Command hooks do **not** fail open. A non-zero exit code (other than 0 or 2) pro Hooks from multiple config files are merged in this order (later layers win on name collisions): -1. **`~/.fabro/cli.toml`** or **`~/.fabro/server.toml`** — global defaults +1. **`~/.fabro/user.toml`** or **`~/.fabro/server.toml`** — global defaults 2. **`fabro.toml`** — project-level overrides 3. **`workflow.toml`** — per-workflow overrides diff --git a/docs/agents/mcp.mdx b/docs/agents/mcp.mdx index 0bc01313d..188438c47 100644 --- a/docs/agents/mcp.mdx +++ b/docs/agents/mcp.mdx @@ -30,7 +30,7 @@ For example, a server named `filesystem` exposing a `read_file` tool becomes `mc MCP servers can be configured in two places: -- **`~/.fabro/cli.toml`** — applies to `fabro exec` sessions. See [CLI Configuration](/reference/cli-configuration#mcp_servers-section). +- **`~/.fabro/user.toml`** — applies to `fabro exec` sessions. See [User Configuration](/reference/user-configuration#mcp_servers-section). - **Run config TOML** — applies to workflow runs (`fabro run`). See [Run Configuration](/execution/run-configuration#mcp_servers). Each server entry specifies a transport type and optional timeouts. The server name is the TOML table key and is used in qualified tool names. diff --git a/docs/docs.json b/docs/docs.json index a02db282f..1c8e3e540 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -105,7 +105,7 @@ "pages": [ "reference/dot-language", "reference/cli", - "reference/cli-configuration", + "reference/user-configuration", "reference/run-directory", "reference/sdk", "reference/architecture", diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index 753af2bd9..106a7dc4e 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -12,7 +12,7 @@ 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. | +| `--server-url ` | Fabro API server URL (overrides `server.base_url` from `user.toml`). Implies server mode. | | `-h, --help` | Print help | | `-V, --version` | Print version | @@ -20,9 +20,9 @@ Fabro loads environment variables from `~/.fabro/.env`. ## Configuration -CLI defaults can be set in `~/.fabro/cli.toml` so you don't have to pass common flags every time: +CLI defaults can be set in `~/.fabro/user.toml` so you don't have to pass common flags every time: -```toml title="cli.toml" +```toml title="user.toml" [exec] provider = "anthropic" model = "claude-opus-4-6" @@ -33,7 +33,7 @@ output_format = "text" model = "claude-sonnet-4-5" ``` -CLI flags always override `cli.toml` values, which override hardcoded defaults. +CLI flags always override `user.toml` values, which override hardcoded defaults. --- @@ -47,7 +47,7 @@ fabro config show demo fabro config show run.toml ``` -With no argument, Fabro prints the merged ambient defaults from `~/.fabro/cli.toml` and the nearest `fabro.toml`. +With no argument, Fabro prints the merged ambient defaults from `~/.fabro/user.toml` and the nearest `fabro.toml`. When you pass a workflow name or path: @@ -750,7 +750,7 @@ fabro upgrade --version 0.6.0 | `--force` | Upgrade even if already on the target version | | `--dry-run` | Preview what would happen without making changes | -Fabro refuses to downgrade unless you specify an explicit `--version`. A daily background check notifies you when a new version is available — disable it with `upgrade_check = false` in [`cli.toml`](/reference/cli-configuration#upgrade_check) or the `--no-upgrade-check` global flag. +Fabro refuses to downgrade unless you specify an explicit `--version`. A daily background check notifies you when a new version is available — disable it with `upgrade_check = false` in [`user.toml`](/reference/user-configuration#upgrade_check) or the `--no-upgrade-check` global flag. ## `fabro asset list` diff --git a/docs/reference/cli-configuration.mdx b/docs/reference/user-configuration.mdx similarity index 91% rename from docs/reference/cli-configuration.mdx rename to docs/reference/user-configuration.mdx index 2988c77a3..96623b54e 100644 --- a/docs/reference/cli-configuration.mdx +++ b/docs/reference/user-configuration.mdx @@ -1,25 +1,25 @@ --- -title: "CLI Configuration" -description: "Configure default settings for the Fabro CLI with cli.toml" +title: "User Configuration" +description: "Configure default user settings for Fabro with user.toml" --- -Fabro loads CLI defaults from `~/.fabro/cli.toml` so you don't have to pass common flags every time. The file is optional — if it doesn't exist, built-in defaults are used. +Fabro loads user defaults from `~/.fabro/user.toml` so you don't have to pass common flags every time. The file is optional — if it doesn't exist, built-in defaults are used. ## File location -The default path is `~/.fabro/cli.toml`. Fabro silently skips loading if the file is missing. +The default path is `~/.fabro/user.toml`. Fabro silently skips loading if the file is missing. ## Precedence CLI flags always take the highest priority: 1. **CLI flags** — always win -2. **`cli.toml`** — used when no flag is provided +2. **`user.toml`** — used when no flag is provided 3. **Built-in defaults** — used when neither flag nor config is set ## Full example -```toml title="cli.toml" +```toml title="user.toml" verbose = true upgrade_check = true mode = "server" @@ -184,7 +184,7 @@ Optional mTLS configuration for authenticating with the server. When present, th Paths support `~/` expansion. Example: -```toml title="cli.toml" +```toml title="user.toml" [server.tls] cert = "~/.fabro/tls/client.crt" key = "~/.fabro/tls/client.key" @@ -195,7 +195,7 @@ ca = "~/.fabro/tls/ca.crt" Enable auto-PR globally so workflows open a GitHub pull request on successful completion — even when running with a `.fabro` file instead of a `run.toml`. -```toml title="cli.toml" +```toml title="user.toml" [pull_request] enabled = true ``` @@ -204,7 +204,7 @@ enabled = true |---|---|---| | `enabled` | Automatically create a PR after successful runs | `false` | -Precedence: `run.toml` > `fabro.toml` (project config) > `cli.toml` > `server.toml` > built-in default (`false`). +Precedence: `run.toml` > `fabro.toml` (project config) > `user.toml` > `server.toml` > built-in default (`false`). ## `[mcp_servers]` section @@ -214,7 +214,7 @@ Configure [MCP servers](/agents/mcp) to connect to during `fabro exec` sessions. Spawn a local process and communicate over stdin/stdout: -```toml title="cli.toml" +```toml title="user.toml" [mcp_servers.filesystem] type = "stdio" command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/workspace"] @@ -237,7 +237,7 @@ NODE_ENV = "production" Connect to a remote MCP server over Streamable HTTP: -```toml title="cli.toml" +```toml title="user.toml" [mcp_servers.sentry] type = "http" url = "https://mcp.sentry.dev/mcp" @@ -256,7 +256,7 @@ Authorization = "Bearer sk-xxx" ### Sandbox transport -Run an MCP server inside the workflow's sandbox and connect via preview URL. Only available with remote sandbox providers ([Daytona](/integrations/daytona)) that support port previews. Typically configured in [run config TOML](/execution/run-configuration#mcp_servers) rather than `cli.toml`. +Run an MCP server inside the workflow's sandbox and connect via preview URL. Only available with remote sandbox providers ([Daytona](/integrations/daytona)) that support port previews. Typically configured in [run config TOML](/execution/run-configuration#mcp_servers) rather than `user.toml`. ```toml title="run.toml" [mcp_servers.playwright] diff --git a/lib/crates/fabro-agent/src/cli.rs b/lib/crates/fabro-agent/src/cli.rs index 51308c1c5..5865cfe11 100644 --- a/lib/crates/fabro-agent/src/cli.rs +++ b/lib/crates/fabro-agent/src/cli.rs @@ -68,10 +68,10 @@ struct Cli { args: AgentArgs, } -pub use fabro_config::cli::{OutputFormat, PermissionLevel}; +pub use fabro_config::user::{OutputFormat, PermissionLevel}; impl AgentArgs { - /// Fill `None` fields from cli.toml values, then hardcoded defaults. + /// Fill `None` fields from user.toml values, then hardcoded defaults. pub fn apply_cli_defaults( &mut self, provider: Option<&str>, diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs index b64d9995b..53ab33cdb 100644 --- a/lib/crates/fabro-cli/src/args.rs +++ b/lib/crates/fabro-cli/src/args.rs @@ -38,7 +38,7 @@ pub(crate) struct GlobalArgs { pub storage_dir: Option, #[cfg(feature = "server")] - /// Server URL (overrides server.base_url from cli.toml) + /// Server URL (overrides server.base_url from user.toml) #[arg( long, global = true, diff --git a/lib/crates/fabro-cli/src/commands/asset/cp.rs b/lib/crates/fabro-cli/src/commands/asset/cp.rs index 387f90cf5..051dfb9ac 100644 --- a/lib/crates/fabro-cli/src/commands/asset/cp.rs +++ b/lib/crates/fabro-cli/src/commands/asset/cp.rs @@ -7,11 +7,11 @@ use fabro_workflows::assets::{AssetEntry, scan_assets}; use fabro_workflows::run_lookup::{resolve_run, runs_base}; use crate::args::{AssetCpArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::split_run_path; +use crate::user_config::load_user_settings_with_globals; pub(super) fn cp_command(args: &AssetCpArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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 cbffa7b93..cffeda2c6 100644 --- a/lib/crates/fabro-cli/src/commands/asset/list.rs +++ b/lib/crates/fabro-cli/src/commands/asset/list.rs @@ -5,11 +5,11 @@ use fabro_workflows::assets::scan_assets; use fabro_workflows::run_lookup::{resolve_run, runs_base}; use crate::args::{AssetListArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; +use crate::user_config::load_user_settings_with_globals; pub(super) fn list_command(args: &AssetListArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/config/mod.rs b/lib/crates/fabro-cli/src/commands/config/mod.rs index c1b445053..38d54746d 100644 --- a/lib/crates/fabro-cli/src/commands/config/mod.rs +++ b/lib/crates/fabro-cli/src/commands/config/mod.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::path::Path; use crate::args::{ConfigCommand, ConfigNamespace, ConfigShowArgs, GlobalArgs}; -use crate::cli_config; +use crate::user_config; use fabro_config::{ConfigLayer, FabroSettings}; pub(crate) fn dispatch(ns: ConfigNamespace, globals: &GlobalArgs) -> anyhow::Result<()> { @@ -17,7 +17,7 @@ fn merged_config(workflow: Option<&Path>, globals: &GlobalArgs) -> anyhow::Resul Some(path) => ConfigLayer::for_workflow(path, &cwd)?, None => ConfigLayer::project(&cwd)?, }; - let cli = cli_config::cli_layer_with_globals(globals)?; + let cli = user_config::user_layer_with_globals(globals)?; base.combine(cli).resolve() } diff --git a/lib/crates/fabro-cli/src/commands/doctor.rs b/lib/crates/fabro-cli/src/commands/doctor.rs index 6d4c57bdc..c9a8a7adc 100644 --- a/lib/crates/fabro-cli/src/commands/doctor.rs +++ b/lib/crates/fabro-cli/src/commands/doctor.rs @@ -21,7 +21,7 @@ use regex::Regex; #[cfg(feature = "server")] use semver::Version; -use crate::cli_config::load_cli_settings; +use crate::user_config::load_user_settings; // --------------------------------------------------------------------------- // System dependency types and parsers (server mode only) @@ -194,23 +194,46 @@ fn apply_live_result( } } -pub(crate) fn check_config(path: Option) -> CheckResult { - match path { - Some(p) => CheckResult { +pub(crate) fn check_config( + user_path: Option, + legacy_path: Option, +) -> CheckResult { + match (user_path, legacy_path) { + (Some(p), None) => CheckResult { name: "Configuration".to_string(), status: CheckStatus::Pass, summary: p.display().to_string(), details: vec![CheckDetail::new(format!("Loaded from {}", p.display()))], remediation: None, }, - None => CheckResult { + (Some(p), Some(legacy)) => CheckResult { name: "Configuration".to_string(), status: CheckStatus::Warning, - summary: "no config file found".to_string(), + summary: p.display().to_string(), + details: vec![ + CheckDetail::new(format!("Loaded from {}", p.display())), + CheckDetail::new(format!("Ignoring legacy config file {}", legacy.display())), + ], + remediation: Some(format!("Delete or rename {}", legacy.display())), + }, + (None, Some(legacy)) => CheckResult { + name: "Configuration".to_string(), + status: CheckStatus::Warning, + summary: "legacy config file ignored".to_string(), + details: vec![ + CheckDetail::new(format!("Found legacy config file {}", legacy.display())), + CheckDetail::new("Rename it to ~/.fabro/user.toml".to_string()), + ], + remediation: Some(format!("Rename {} to ~/.fabro/user.toml", legacy.display())), + }, + (None, None) => CheckResult { + name: "Configuration".to_string(), + status: CheckStatus::Warning, + summary: "no user config file found".to_string(), details: vec![CheckDetail::new( - "Create ~/.fabro/cli.toml to configure Arc".to_string(), + "Create ~/.fabro/user.toml to configure Fabro".to_string(), )], - remediation: Some("Create ~/.fabro/cli.toml".to_string()), + remediation: Some("Create ~/.fabro/user.toml".to_string()), }, } } @@ -938,10 +961,12 @@ pub(crate) async fn run_doctor(verbose: bool, live: bool) -> i32 { spinner.enable_steady_tick(std::time::Duration::from_millis(80)); // Gather state - let cli_settings = load_cli_settings().unwrap_or_default(); + let cli_settings = load_user_settings().unwrap_or_default(); - let config_path = dirs::home_dir().map(|h| h.join(".fabro").join("cli.toml")); - let config_exists = config_path.as_ref().is_some_and(|p| p.exists()); + let user_config_path = fabro_config::user::default_user_config_path(); + let user_config_exists = user_config_path.as_ref().is_some_and(|p| p.exists()); + let legacy_config_path = fabro_config::user::legacy_user_config_path(); + let legacy_config_exists = legacy_config_path.as_ref().is_some_and(|p| p.exists()); let llm_statuses: Vec<(Provider, bool)> = Provider::ALL .iter() @@ -1142,7 +1167,18 @@ pub(crate) async fn run_doctor(verbose: bool, live: bool) -> i32 { CheckSection { title: "Required".into(), checks: vec![ - check_config(if config_exists { config_path } else { None }), + check_config( + if user_config_exists { + user_config_path + } else { + None + }, + if legacy_config_exists { + legacy_config_path + } else { + None + }, + ), check_llm_providers(&llm_statuses, llm_live_results.as_deref()), check_github_app(&github_status), ], @@ -1195,18 +1231,31 @@ mod tests { #[test] fn check_config_pass_with_path() { - let result = check_config(Some(PathBuf::from("/home/user/.fabro/cli.toml"))); + let result = check_config(Some(PathBuf::from("/home/user/.fabro/user.toml")), None); assert_eq!(result.status, CheckStatus::Pass); - assert!(result.summary.contains(".fabro/cli.toml")); + assert!(result.summary.contains(".fabro/user.toml")); } #[test] fn check_config_warning_without_path() { - let result = check_config(None); + let result = check_config(None, None); assert_eq!(result.status, CheckStatus::Warning); assert!(result.remediation.is_some()); } + #[test] + fn check_config_warning_for_legacy_only_path() { + let result = check_config(None, Some(PathBuf::from("/home/user/.fabro/cli.toml"))); + assert_eq!(result.status, CheckStatus::Warning); + assert!(result.summary.contains("legacy")); + assert!( + result + .remediation + .as_deref() + .is_some_and(|remediation| remediation.contains(".fabro/user.toml")) + ); + } + // -- check_llm_providers -- #[test] diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index 1551e0f18..5eb9ca596 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -6,10 +6,10 @@ use fabro_config::mcp::McpServerEntry; use fabro_mcp::config::McpServerConfig; use crate::args::GlobalArgs; -use crate::cli_config; +use crate::user_config; pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = cli_config::load_cli_settings_with_globals(globals)?; + let cli_settings = user_config::load_user_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(); @@ -20,7 +20,7 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result exec_defaults.and_then(|a| a.output_format), ); #[cfg(feature = "server")] - let resolved = cli_config::resolve_mode( + let resolved = user_config::resolve_mode( globals.storage_dir.as_deref(), globals.server_url.as_deref(), &cli_settings, @@ -33,9 +33,9 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result #[cfg(feature = "server")] { match resolved.mode { - cli_config::ExecutionMode::Server => { + user_config::ExecutionMode::Server => { tracing::info!(mode = "server", "Agent session starting"); - let http_client = cli_config::build_server_client(resolved.tls.as_ref())?; + let http_client = user_config::build_server_client(resolved.tls.as_ref())?; let provider_name = args .provider .clone() @@ -53,7 +53,7 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result .map_err(|e| anyhow::anyhow!("Failed to register fabro server adapter: {e}"))?; run_with_args_and_client(args, Some(client), mcp_servers).await? } - cli_config::ExecutionMode::Standalone => { + user_config::ExecutionMode::Standalone => { tracing::info!(mode = "standalone", "Agent session starting"); run_with_args(args, mcp_servers).await? } diff --git a/lib/crates/fabro-cli/src/commands/graph.rs b/lib/crates/fabro-cli/src/commands/graph.rs index d37ad6f66..90a026772 100644 --- a/lib/crates/fabro-cli/src/commands/graph.rs +++ b/lib/crates/fabro-cli/src/commands/graph.rs @@ -20,7 +20,7 @@ static RANKDIR_RE: LazyLock = pub(crate) fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> { let cwd = std::env::current_dir()?; let settings = ConfigLayer::for_workflow(&args.workflow, &cwd)? - .combine(ConfigLayer::cli()?) + .combine(ConfigLayer::user()?) .resolve()?; let resolution = resolve_workflow_path(&args.workflow, &cwd)?; let validated = validate(ValidateInput { diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index 694c857d1..292bdcf71 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -439,28 +439,31 @@ async fn setup_github_app( .context("missing 'pem' in GitHub response")? .to_string(); - // Write non-secret config to cli.toml - let cli_toml_path = arc_dir.join("cli.toml"); - let existing = std::fs::read_to_string(&cli_toml_path).unwrap_or_default(); + // Write non-secret config to user.toml + let user_toml_path = arc_dir.join(fabro_config::user::USER_CONFIG_FILENAME); + let existing = std::fs::read_to_string(&user_toml_path).unwrap_or_default(); let mut doc: toml::Value = if existing.is_empty() { toml::Value::Table(toml::Table::default()) } else { - toml::from_str(&existing).context("failed to parse existing cli.toml")? + toml::from_str(&existing).context("failed to parse existing user.toml")? }; - let table = doc.as_table_mut().context("cli.toml root is not a table")?; + let table = doc + .as_table_mut() + .context("user.toml root is not a table")?; let git = table .entry("git") .or_insert(toml::Value::Table(toml::Table::default())); let git_table = git .as_table_mut() - .context("cli.toml [git] is not a table")?; + .context("user.toml [git] is not a table")?; git_table.insert("app_id".into(), toml::Value::String(app_id)); git_table.insert("slug".into(), toml::Value::String(slug.clone())); git_table.insert("client_id".into(), toml::Value::String(client_id)); - std::fs::write(&cli_toml_path, toml::to_string_pretty(&doc)?)?; + std::fs::write(&user_toml_path, toml::to_string_pretty(&doc)?)?; eprintln!( " {}", - s.dim.apply_to(format!("Wrote {}", cli_toml_path.display())) + s.dim + .apply_to(format!("Wrote {}", user_toml_path.display())) ); eprintln!( " {}", @@ -649,8 +652,8 @@ pub(crate) async fn run_install(web_url: &str) -> Result<()> { if setup_github { let github_env_pairs = setup_github_app(&arc_dir, &s, web_url).await?; let slug = { - let cli_toml_path = arc_dir.join("cli.toml"); - let toml_content = std::fs::read_to_string(&cli_toml_path).unwrap_or_default(); + let user_toml_path = arc_dir.join(fabro_config::user::USER_CONFIG_FILENAME); + let toml_content = std::fs::read_to_string(&user_toml_path).unwrap_or_default(); let doc: toml::Value = toml::from_str(&toml_content) .unwrap_or(toml::Value::Table(toml::Table::default())); doc.get("git") diff --git a/lib/crates/fabro-cli/src/commands/llm/chat.rs b/lib/crates/fabro-cli/src/commands/llm/chat.rs index 7b56dace3..5cff44972 100644 --- a/lib/crates/fabro-cli/src/commands/llm/chat.rs +++ b/lib/crates/fabro-cli/src/commands/llm/chat.rs @@ -18,21 +18,21 @@ pub(super) async fn execute( #[cfg(feature = "server")] { - let resolved = crate::cli_config::resolve_mode( + let resolved = crate::user_config::resolve_mode( globals.storage_dir.as_deref(), globals.server_url.as_deref(), cli_settings, ); match resolved.mode { - crate::cli_config::ExecutionMode::Server => { - let client = crate::cli_config::build_server_client(resolved.tls.as_ref())?; + crate::user_config::ExecutionMode::Server => { + let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; let server = ServerConnection { client, base_url: resolved.server_base_url, }; run_chat_via_server(args, &server).await?; } - crate::cli_config::ExecutionMode::Standalone => { + crate::user_config::ExecutionMode::Standalone => { run_chat(args).await?; } } diff --git a/lib/crates/fabro-cli/src/commands/llm/mod.rs b/lib/crates/fabro-cli/src/commands/llm/mod.rs index 64ec710a2..e86c603fe 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_with_globals; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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 c8dd636b8..06333258c 100644 --- a/lib/crates/fabro-cli/src/commands/llm/prompt.rs +++ b/lib/crates/fabro-cli/src/commands/llm/prompt.rs @@ -18,21 +18,21 @@ pub(super) async fn execute( #[cfg(feature = "server")] { - let resolved = crate::cli_config::resolve_mode( + let resolved = crate::user_config::resolve_mode( globals.storage_dir.as_deref(), globals.server_url.as_deref(), cli_settings, ); match resolved.mode { - crate::cli_config::ExecutionMode::Server => { - let client = crate::cli_config::build_server_client(resolved.tls.as_ref())?; + crate::user_config::ExecutionMode::Server => { + let client = crate::user_config::build_server_client(resolved.tls.as_ref())?; let server = ServerConnection { client, base_url: resolved.server_base_url, }; run_prompt_via_server(args, &server).await?; } - crate::cli_config::ExecutionMode::Standalone => { + crate::user_config::ExecutionMode::Standalone => { run_prompt(args).await?; } } diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 20e2a60ae..fc247708d 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -5,27 +5,27 @@ use fabro_llm::cli::{ModelsCommand, run_models}; use crate::args::GlobalArgs; #[cfg(feature = "server")] -use crate::cli_config; +use crate::user_config; pub(crate) async fn execute(command: Option, globals: &GlobalArgs) -> Result<()> { let server = { #[cfg(feature = "server")] { - let cli_settings = cli_config::load_cli_settings_with_globals(globals)?; - let resolved = cli_config::resolve_mode( + let cli_settings = user_config::load_user_settings_with_globals(globals)?; + let resolved = user_config::resolve_mode( globals.storage_dir.as_deref(), globals.server_url.as_deref(), &cli_settings, ); match resolved.mode { - cli_config::ExecutionMode::Server => { - let client = cli_config::build_server_client(resolved.tls.as_ref())?; + user_config::ExecutionMode::Server => { + let client = user_config::build_server_client(resolved.tls.as_ref())?; Some(ServerConnection { client, base_url: resolved.server_base_url, }) } - cli_config::ExecutionMode::Standalone => None, + user_config::ExecutionMode::Standalone => None, } } #[cfg(not(feature = "server"))] diff --git a/lib/crates/fabro-cli/src/commands/pr/close.rs b/lib/crates/fabro-cli/src/commands/pr/close.rs index 25f16aa92..60034c2ae 100644 --- a/lib/crates/fabro-cli/src/commands/pr/close.rs +++ b/lib/crates/fabro-cli/src/commands/pr/close.rs @@ -6,14 +6,14 @@ use fabro_workflows::run_lookup::runs_base; use tracing::info; use crate::args::{GlobalArgs, PrCloseArgs}; -use crate::cli_config::load_cli_settings_with_globals; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn close_command( args: PrCloseArgs, github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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 1e50e6df1..d8776e3cf 100644 --- a/lib/crates/fabro-cli/src/commands/pr/create.rs +++ b/lib/crates/fabro-cli/src/commands/pr/create.rs @@ -13,15 +13,15 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; use crate::args::{GlobalArgs, PrCreateArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn create_command( args: PrCreateArgs, github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); create_from(&base, args, github_app).await } diff --git a/lib/crates/fabro-cli/src/commands/pr/list.rs b/lib/crates/fabro-cli/src/commands/pr/list.rs index f56df247e..bfb9a56bb 100644 --- a/lib/crates/fabro-cli/src/commands/pr/list.rs +++ b/lib/crates/fabro-cli/src/commands/pr/list.rs @@ -8,15 +8,15 @@ use futures::future::join_all; use tracing::info; use crate::args::{GlobalArgs, PrListArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn list_command( args: PrListArgs, github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&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 2dfa4ee8f..a6d8a944e 100644 --- a/lib/crates/fabro-cli/src/commands/pr/merge.rs +++ b/lib/crates/fabro-cli/src/commands/pr/merge.rs @@ -7,14 +7,14 @@ use tracing::info; use fabro_workflows::run_lookup::runs_base; use crate::args::{GlobalArgs, PrMergeArgs}; -use crate::cli_config::load_cli_settings_with_globals; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn merge_command( args: PrMergeArgs, github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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 94be1b382..bceb19684 100644 --- a/lib/crates/fabro-cli/src/commands/pr/mod.rs +++ b/lib/crates/fabro-cli/src/commands/pr/mod.rs @@ -12,12 +12,12 @@ use fabro_workflows::pull_request::PullRequestRecord; use fabro_workflows::run_lookup::resolve_run_combined; 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; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn dispatch(ns: PrNamespace, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let github_app = build_github_app_credentials(cli_settings.app_id()); match ns.command { diff --git a/lib/crates/fabro-cli/src/commands/pr/view.rs b/lib/crates/fabro-cli/src/commands/pr/view.rs index 9cce8c995..c977a6554 100644 --- a/lib/crates/fabro-cli/src/commands/pr/view.rs +++ b/lib/crates/fabro-cli/src/commands/pr/view.rs @@ -7,14 +7,14 @@ use tracing::info; use fabro_workflows::run_lookup::runs_base; use crate::args::{GlobalArgs, PrViewArgs}; -use crate::cli_config::load_cli_settings_with_globals; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn view_command( args: PrViewArgs, github_app: Option, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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 77da2d39e..8b980e8c3 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -17,13 +17,13 @@ use fabro_workflows::git::{GitSyncStatus, sync_status}; use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate}; use crate::args::{GlobalArgs, PreflightArgs}; -use crate::cli_config::{cli_layer_with_globals, load_cli_settings_with_globals}; use crate::shared::github::build_github_app_credentials; +use crate::user_config::{load_user_settings_with_globals, user_layer_with_globals}; 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 = cli_layer_with_globals(globals)?; - let cli_settings: FabroSettings = load_cli_settings_with_globals(globals)?; + let cli = user_layer_with_globals(globals)?; + let cli_settings: FabroSettings = load_user_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/repo/init.rs b/lib/crates/fabro-cli/src/commands/repo/init.rs index 1bd4f1c40..a24aca886 100644 --- a/lib/crates/fabro-cli/src/commands/repo/init.rs +++ b/lib/crates/fabro-cli/src/commands/repo/init.rs @@ -3,8 +3,8 @@ use std::path::PathBuf; use anyhow::{Context, Result, bail}; use tokio::task::spawn_blocking; -use crate::cli_config::load_cli_settings; use crate::shared::github::build_github_app_credentials; +use crate::user_config::load_user_settings; pub(super) fn git_repo_root() -> Result { let output = std::process::Command::new("git") @@ -153,7 +153,7 @@ async fn check_github_app_installation() { }; // Load CLI config to get app_id and slug - let Ok(cli_settings) = load_cli_settings() else { + let Ok(cli_settings) = load_user_settings() else { return; }; diff --git a/lib/crates/fabro-cli/src/commands/run/command.rs b/lib/crates/fabro-cli/src/commands/run/command.rs index 7d58c920d..9964ae088 100644 --- a/lib/crates/fabro-cli/src/commands/run/command.rs +++ b/lib/crates/fabro-cli/src/commands/run/command.rs @@ -2,12 +2,12 @@ use anyhow::Result; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, RunArgs}; -use crate::cli_config::{self, cli_layer_with_globals}; +use crate::user_config::{self, user_layer_with_globals}; 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 = cli_layer_with_globals(globals)?; + let cli_settings = user_config::load_user_settings_with_globals(globals)?; + let cli = user_layer_with_globals(globals)?; args.verbose = args.verbose || cli_settings.verbose_enabled(); let quiet = args.detach; diff --git a/lib/crates/fabro-cli/src/commands/run/cp.rs b/lib/crates/fabro-cli/src/commands/run/cp.rs index 31a31a0b5..c5cf1a015 100644 --- a/lib/crates/fabro-cli/src/commands/run/cp.rs +++ b/lib/crates/fabro-cli/src/commands/run/cp.rs @@ -10,8 +10,8 @@ use tokio::fs; use tracing::{debug, info}; use crate::args::{CpArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::split_run_path; +use crate::user_config::load_user_settings_with_globals; enum CopyDirection { Download { @@ -28,7 +28,7 @@ enum CopyDirection { 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_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); match direction { diff --git a/lib/crates/fabro-cli/src/commands/run/detached.rs b/lib/crates/fabro-cli/src/commands/run/detached.rs index 95e90640d..a6a69e7c3 100644 --- a/lib/crates/fabro-cli/src/commands/run/detached.rs +++ b/lib/crates/fabro-cli/src/commands/run/detached.rs @@ -12,9 +12,9 @@ use fabro_workflows::operations::{ }; use fabro_workflows::records::{RunRecord, RunRecordExt}; -use crate::cli_config; use crate::shared; use crate::store; +use crate::user_config; pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> { let _ = fabro_proctitle::init(); @@ -24,7 +24,7 @@ pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bo }); let run_record = RunRecord::load(&run_dir)?; - let cli_settings = cli_config::load_cli_settings()?; + let cli_settings = user_config::load_user_settings()?; let on_node: fabro_workflows::OnNodeCallback = Some({ let short_id = super::short_run_id(&run_record.run_id).to_string(); fabro_proctitle::set(&format!("fabro: {short_id}")); diff --git a/lib/crates/fabro-cli/src/commands/run/diff.rs b/lib/crates/fabro-cli/src/commands/run/diff.rs index f8b9787b0..07fef22c6 100644 --- a/lib/crates/fabro-cli/src/commands/run/diff.rs +++ b/lib/crates/fabro-cli/src/commands/run/diff.rs @@ -11,12 +11,12 @@ use fabro_workflows::sandbox_git::GIT_REMOTE; use tracing::{debug, info}; use crate::args::{DiffArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn run(args: DiffArgs, globals: &GlobalArgs) -> Result<()> { info!(run_id = %args.run, "Showing diff"); - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index 24eb0a6cd..12259c686 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -9,12 +9,12 @@ use fabro_workflows::operations::{ use git2::Repository; use crate::args::{ForkArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store::{build_store, open_run_reader}; +use crate::user_config::load_user_settings_with_globals; 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_with_globals(globals)?; + let cli_settings = load_user_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 32388627e..a71b8c5cd 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -13,11 +13,11 @@ use tokio::time; use tracing::{debug, info, warn}; use crate::args::{GlobalArgs, LogsArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn run(args: &LogsArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/run/mod.rs b/lib/crates/fabro-cli/src/commands/run/mod.rs index 2e25ada0a..9487cf177 100644 --- a/lib/crates/fabro-cli/src/commands/run/mod.rs +++ b/lib/crates/fabro-cli/src/commands/run/mod.rs @@ -4,8 +4,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::{cli_layer_with_globals, load_cli_settings_with_globals}; use crate::store; +use crate::user_config::{load_user_settings_with_globals, user_layer_with_globals}; pub(crate) mod attach; pub(crate) mod command; @@ -35,13 +35,13 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( RunCommands::Run(args) => command::execute(args, globals).await, RunCommands::Create(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - let cli = cli_layer_with_globals(globals)?; + let cli = user_layer_with_globals(globals)?; let (run_id, _run_dir) = create::create_run(&args, cli, styles, true)?; println!("{run_id}"); Ok(()) } RunCommands::Start { run } => { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&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?; @@ -51,7 +51,7 @@ 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_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&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?; @@ -80,7 +80,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; crate::sleep_inhibitor::guard(cli_settings.prevent_idle_sleep_enabled()) }; resume::resume_command(args, styles, globals).await diff --git a/lib/crates/fabro-cli/src/commands/run/preview.rs b/lib/crates/fabro-cli/src/commands/run/preview.rs index 7567d3eaa..4c3597019 100644 --- a/lib/crates/fabro-cli/src/commands/run/preview.rs +++ b/lib/crates/fabro-cli/src/commands/run/preview.rs @@ -6,12 +6,12 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; use crate::args::{GlobalArgs, PreviewArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::validate_daytona_provider; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/run/resume.rs b/lib/crates/fabro-cli/src/commands/run/resume.rs index 282fe3d87..ab9fe1444 100644 --- a/lib/crates/fabro-cli/src/commands/run/resume.rs +++ b/lib/crates/fabro-cli/src/commands/run/resume.rs @@ -5,8 +5,8 @@ use fabro_workflows::records::{RunRecord, RunRecordExt}; use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use crate::args::{GlobalArgs, ResumeArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; /// Resume an interrupted workflow run. /// @@ -18,7 +18,7 @@ pub(crate) async fn resume_command( styles: &'static Styles, globals: &GlobalArgs, ) -> anyhow::Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index eefce621b..19fbe5af5 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -12,13 +12,13 @@ use fabro_workflows::operations::{ use git2::Repository; 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}; +use crate::user_config::load_user_settings_with_globals; 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_with_globals(globals)?; + let cli_settings = load_user_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 b016e19e8..659ec883e 100644 --- a/lib/crates/fabro-cli/src/commands/run/ssh.rs +++ b/lib/crates/fabro-cli/src/commands/run/ssh.rs @@ -6,12 +6,12 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use tracing::info; use crate::args::{GlobalArgs, SshArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::validate_daytona_provider; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index 29a243c56..fc33f591e 100644 --- a/lib/crates/fabro-cli/src/commands/run/wait.rs +++ b/lib/crates/fabro-cli/src/commands/run/wait.rs @@ -9,12 +9,12 @@ use fabro_workflows::run_status::{RunStatus, RunStatusRecord, RunStatusRecordExt use tracing::info; use crate::args::{GlobalArgs, WaitArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_duration_ms; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn run(args: &WaitArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&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?; diff --git a/lib/crates/fabro-cli/src/commands/runs/inspect.rs b/lib/crates/fabro-cli/src/commands/runs/inspect.rs index 6482e6b94..5619ed170 100644 --- a/lib/crates/fabro-cli/src/commands/runs/inspect.rs +++ b/lib/crates/fabro-cli/src/commands/runs/inspect.rs @@ -11,8 +11,8 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use fabro_workflows::run_status::RunStatus; use crate::args::{GlobalArgs, InspectArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; #[derive(Debug, Serialize)] pub(crate) struct InspectOutput { @@ -27,7 +27,7 @@ pub(crate) struct InspectOutput { } pub(crate) async fn run(args: &InspectArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/runs/list.rs b/lib/crates/fabro-cli/src/commands/runs/list.rs index 40d1a3d7e..7893dff51 100644 --- a/lib/crates/fabro-cli/src/commands/runs/list.rs +++ b/lib/crates/fabro-cli/src/commands/runs/list.rs @@ -12,9 +12,9 @@ use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_run use fabro_workflows::run_status::RunStatus; 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 crate::user_config::load_user_settings_with_globals; use super::short_run_id; @@ -23,7 +23,7 @@ pub(crate) async fn list_command( styles: &Styles, globals: &GlobalArgs, ) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); let store = store::build_store(&cli_settings.storage_dir())?; let runs = scan_runs_combined(store.as_ref(), &base).await?; diff --git a/lib/crates/fabro-cli/src/commands/runs/rm.rs b/lib/crates/fabro-cli/src/commands/runs/rm.rs index 2f5b5e6a8..2fc6065f4 100644 --- a/lib/crates/fabro-cli/src/commands/runs/rm.rs +++ b/lib/crates/fabro-cli/src/commands/runs/rm.rs @@ -11,13 +11,13 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use fabro_workflows::run_status::{RunStatus, RunStatusRecord, write_run_status}; use crate::args::{GlobalArgs, RunsRemoveArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; use super::short_run_id; pub(crate) async fn remove_command(args: &RunsRemoveArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&cli_settings.storage_dir()); let store = store::build_store(&cli_settings.storage_dir())?; remove_from(args, store.as_ref(), &base).await diff --git a/lib/crates/fabro-cli/src/commands/store/dump.rs b/lib/crates/fabro-cli/src/commands/store/dump.rs index eaa2958c1..5f4aa6170 100644 --- a/lib/crates/fabro-cli/src/commands/store/dump.rs +++ b/lib/crates/fabro-cli/src/commands/store/dump.rs @@ -8,11 +8,11 @@ use fabro_workflows::run_lookup::{resolve_run_combined, runs_base}; use serde::Serialize; use crate::args::{GlobalArgs, StoreDumpArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(crate) async fn dump_command(args: &StoreDumpArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index 5d4d891d9..007f6d726 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -10,12 +10,12 @@ use fabro_workflows::run_lookup::{logs_base, runs_base, scan_runs_combined}; use fabro_workflows::run_status::RunStatus; use crate::args::{DfArgs, GlobalArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn df_command(args: &DfArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_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); diff --git a/lib/crates/fabro-cli/src/commands/system/prune.rs b/lib/crates/fabro-cli/src/commands/system/prune.rs index 809056715..66a30625e 100644 --- a/lib/crates/fabro-cli/src/commands/system/prune.rs +++ b/lib/crates/fabro-cli/src/commands/system/prune.rs @@ -9,12 +9,12 @@ use tracing::{debug, info}; use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_runs_combined}; use crate::args::{GlobalArgs, RunsPruneArgs}; -use crate::cli_config::load_cli_settings_with_globals; use crate::shared::format_size; use crate::store; +use crate::user_config::load_user_settings_with_globals; pub(super) async fn prune_command(args: &RunsPruneArgs, globals: &GlobalArgs) -> Result<()> { - let cli_settings = load_cli_settings_with_globals(globals)?; + let cli_settings = load_user_settings_with_globals(globals)?; let base = runs_base(&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/commands/validate.rs b/lib/crates/fabro-cli/src/commands/validate.rs index 1ae46609b..7e335d6f7 100644 --- a/lib/crates/fabro-cli/src/commands/validate.rs +++ b/lib/crates/fabro-cli/src/commands/validate.rs @@ -11,7 +11,7 @@ use crate::shared::{print_diagnostics, relative_path}; pub(crate) fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> { let cwd = std::env::current_dir()?; let settings = ConfigLayer::for_workflow(&args.workflow, &cwd)? - .combine(ConfigLayer::cli()?) + .combine(ConfigLayer::user()?) .resolve()?; let resolution = resolve_workflow_path(&args.workflow, &cwd)?; let validated = validate(ValidateInput { diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index e315a3e6b..ee2731d25 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -1,13 +1,13 @@ #![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)] mod args; -mod cli_config; mod commands; mod logging; mod shared; #[cfg(feature = "sleep_inhibitor")] mod sleep_inhibitor; mod store; +mod user_config; use anyhow::Result; use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands}; @@ -115,7 +115,7 @@ async fn main_inner() -> (String, Result<()>) { Err(err) => return (command_name, Err(err)), } } else { - match cli_config::load_cli_settings() { + match user_config::load_user_settings() { Ok(cli_settings) => ( cli_settings.log.as_ref().and_then(|l| l.level.clone()), cli_settings.upgrade_check_enabled(), @@ -126,7 +126,7 @@ async fn main_inner() -> (String, Result<()>) { } #[cfg(not(feature = "server"))] { - match cli_config::load_cli_settings() { + match user_config::load_user_settings() { Ok(cli_settings) => ( cli_settings.log.as_ref().and_then(|l| l.level.clone()), cli_settings.upgrade_check_enabled(), @@ -189,7 +189,7 @@ async fn main_inner() -> (String, Result<()>) { .await?; } Commands::Doctor { verbose, dry_run } => { - let cli_settings = cli_config::load_cli_settings()?; + let cli_settings = user_config::load_user_settings()?; let verbose = verbose || cli_settings.verbose_enabled(); let exit_code = commands::doctor::run_doctor(verbose, !dry_run).await; std::process::exit(exit_code); diff --git a/lib/crates/fabro-cli/src/cli_config.rs b/lib/crates/fabro-cli/src/user_config.rs similarity index 94% rename from lib/crates/fabro-cli/src/cli_config.rs rename to lib/crates/fabro-cli/src/user_config.rs index 359efc5db..eca7685a3 100644 --- a/lib/crates/fabro-cli/src/cli_config.rs +++ b/lib/crates/fabro-cli/src/user_config.rs @@ -2,7 +2,7 @@ use std::path::Path; #[allow(unused_imports)] -pub(crate) use fabro_config::cli::*; +pub(crate) use fabro_config::user::*; use fabro_config::ConfigLayer; use fabro_config::FabroSettings; @@ -12,19 +12,19 @@ use crate::args::GlobalArgs; #[cfg(feature = "server")] use tracing::debug; -pub(crate) fn load_cli_settings() -> anyhow::Result { - ConfigLayer::cli()?.resolve() +pub(crate) fn load_user_settings() -> anyhow::Result { + ConfigLayer::user()?.resolve() } -pub(crate) fn cli_layer_with_globals(globals: &GlobalArgs) -> anyhow::Result { - let layer = ConfigLayer::cli()?; +pub(crate) fn user_layer_with_globals(globals: &GlobalArgs) -> anyhow::Result { + let layer = ConfigLayer::user()?; Ok(apply_global_overrides(layer, globals)) } -pub(crate) fn load_cli_settings_with_globals( +pub(crate) fn load_user_settings_with_globals( globals: &GlobalArgs, ) -> anyhow::Result { - cli_layer_with_globals(globals)?.resolve() + user_layer_with_globals(globals)?.resolve() } pub(crate) fn apply_global_overrides(mut layer: ConfigLayer, globals: &GlobalArgs) -> ConfigLayer { diff --git a/lib/crates/fabro-cli/tests/it/cli.rs b/lib/crates/fabro-cli/tests/it/cli.rs index 66b4547a9..7f6fa8130 100644 --- a/lib/crates/fabro-cli/tests/it/cli.rs +++ b/lib/crates/fabro-cli/tests/it/cli.rs @@ -6,9 +6,9 @@ use std::time::Duration; use assert_cmd::Command; use chrono::TimeZone; use fabro_config::FabroSettings; -#[cfg(feature = "server")] -use fabro_config::cli::ExecutionMode; use fabro_config::mcp::McpTransport; +#[cfg(feature = "server")] +use fabro_config::user::ExecutionMode; use fabro_git_storage::branchstore::BranchStore; use fabro_git_storage::gitobj::Store as GitStore; use fabro_store::{NodeVisitRef, RuntimeState, SlateStore, Store as _}; @@ -36,7 +36,7 @@ fn setup_config_show_fixture() -> (tempfile::TempDir, tempfile::TempDir) { let home_fabro = home.path().join(".fabro"); std::fs::create_dir_all(&home_fabro).unwrap(); std::fs::write( - home_fabro.join("cli.toml"), + home_fabro.join("user.toml"), r#" verbose = true @@ -160,7 +160,7 @@ fn setup_external_workflow_fixture() -> (tempfile::TempDir, tempfile::TempDir, s let home_fabro = home.path().join(".fabro"); std::fs::create_dir_all(&home_fabro).unwrap(); std::fs::write( - home_fabro.join("cli.toml"), + home_fabro.join("user.toml"), format!( r#" storage_dir = "{}" @@ -225,7 +225,7 @@ fn init_cli_home(storage_dir: &Path) -> tempfile::TempDir { std::fs::create_dir_all(&home_fabro).unwrap(); let storage_dir = serde_json::to_string(&storage_dir.to_string_lossy().into_owned()).unwrap(); std::fs::write( - home_fabro.join("cli.toml"), + home_fabro.join("user.toml"), format!("storage_dir = {storage_dir}\n"), ) .unwrap(); @@ -2020,16 +2020,82 @@ fn config_show_missing_run_config_errors() { .stderr(predicate::str::contains("Workflow not found")); } +#[test] +fn config_show_legacy_cli_config_warns_and_ignores_it() { + let home = tempfile::tempdir().unwrap(); + let project = tempfile::tempdir().unwrap(); + + let home_fabro = home.path().join(".fabro"); + std::fs::create_dir_all(&home_fabro).unwrap(); + std::fs::write( + home_fabro.join("cli.toml"), + r#" +verbose = true + +[llm] +model = "legacy-model" +"#, + ) + .unwrap(); + + let assert = arc() + .env("HOME", home.path()) + .current_dir(project.path()) + .args(["config", "show"]) + .assert() + .success() + .stderr(predicate::str::contains("ignoring legacy config file")) + .stderr(predicate::str::contains("Rename it to")); + + let cfg = parse_config_show(&assert.get_output().stdout); + assert_eq!(cfg.verbose, None); + assert_eq!(cfg.llm, None); +} + +#[test] +fn config_show_user_config_wins_over_legacy_cli_config() { + let (home, project) = setup_config_show_fixture(); + std::fs::write( + home.path().join(".fabro").join("cli.toml"), + r#" +[llm] +model = "legacy-model" + +[vars] +shared = "legacy" +"#, + ) + .unwrap(); + + let assert = arc() + .env("HOME", home.path()) + .current_dir(project.path()) + .args(["config", "show"]) + .assert() + .success() + .stderr(predicate::str::contains("ignoring legacy config file")); + + let cfg = parse_config_show(&assert.get_output().stdout); + let llm = cfg.llm.as_ref().expect("llm config"); + assert_eq!(llm.model.as_deref(), Some("project-model")); + assert_eq!( + cfg.vars + .as_ref() + .and_then(|vars| vars.get("shared").map(String::as_str)), + Some("project") + ); +} + #[test] #[cfg(feature = "server")] fn config_show_server_url_overrides_cli_defaults() { let (home, project) = setup_config_show_fixture(); - let cli_toml = home.path().join(".fabro").join("cli.toml"); + let user_toml = home.path().join(".fabro").join("user.toml"); std::fs::write( - &cli_toml, + &user_toml, format!( "{}\nmode = \"standalone\"\n[server]\nbase_url = \"https://config.example.com\"\n", - std::fs::read_to_string(&cli_toml).unwrap() + std::fs::read_to_string(&user_toml).unwrap() ), ) .unwrap(); diff --git a/lib/crates/fabro-cli/tests/it/snapshots/it__cli__serve_help.snap b/lib/crates/fabro-cli/tests/it/snapshots/it__cli__serve_help.snap index e535be0f8..ef7de8301 100644 --- a/lib/crates/fabro-cli/tests/it/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: 745 +assertion_line: 747 expression: stdout --- Start the HTTP API server @@ -9,29 +9,29 @@ Usage: fabro serve [OPTIONS] Options: --debug - Enable DEBUG-level logging (default is INFO) + Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=] --port Port to listen on [default: 3000] --host Host address to bind to [default: 127.0.0.1] --no-upgrade-check - Disable automatic upgrade check + Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=] --model Override default LLM model --quiet - Suppress non-essential output + Suppress non-essential output [env: FABRO_QUIET=] --provider Override default LLM provider --verbose - Enable verbose output + Enable verbose output [env: FABRO_VERBOSE=] --dry-run Execute with simulated LLM backend --storage-dir - Storage directory (default: ~/.fabro) + Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=] --sandbox Sandbox for agent tools --server-url - Server URL (overrides server.base_url from cli.toml) + Server URL (overrides server.base_url from user.toml) [env: FABRO_SERVER_URL=] --max-concurrent-runs Maximum number of concurrent run executions --config diff --git a/lib/crates/fabro-cli/tests/it/trycmd.rs b/lib/crates/fabro-cli/tests/it/trycmd.rs index 70c256b9e..bcacf9dd1 100644 --- a/lib/crates/fabro-cli/tests/it/trycmd.rs +++ b/lib/crates/fabro-cli/tests/it/trycmd.rs @@ -2,7 +2,10 @@ macro_rules! trycmd_subcommand { ($name:ident, $dir:expr) => { #[test] fn $name() { + let home = tempfile::tempdir().unwrap(); + let home = home.path().display().to_string(); trycmd::TestCases::new() + .env("HOME", home) .case(concat!("tests/cmd/", $dir, "/*.trycmd")) .case(concat!("tests/cmd/", $dir, "/*.toml")); } diff --git a/lib/crates/fabro-config/src/cli.rs b/lib/crates/fabro-config/src/cli.rs deleted file mode 100644 index 3a2e40416..000000000 --- a/lib/crates/fabro-config/src/cli.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::path::{Path, PathBuf}; - -use anyhow::anyhow; -use serde::{Deserialize, Serialize}; - -use crate::config::ConfigLayer; - -pub use fabro_types::settings::cli::{ - ClientTlsSettings, ExecSettings, ExecutionMode, OutputFormat, PermissionLevel, ServerSettings, -}; - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] -pub struct ClientTlsConfig { - pub cert: Option, - pub key: Option, - pub ca: Option, -} - -impl TryFrom for ClientTlsSettings { - type Error = anyhow::Error; - - fn try_from(value: ClientTlsConfig) -> Result { - Ok(Self { - cert: value.cert.ok_or_else(|| { - anyhow!("server.tls.cert is required when server.tls is configured") - })?, - key: value.key.ok_or_else(|| { - anyhow!("server.tls.key is required when server.tls is configured") - })?, - ca: value.ca.ok_or_else(|| { - anyhow!("server.tls.ca is required when server.tls is configured") - })?, - }) - } -} - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] -pub struct ServerConfig { - pub base_url: Option, - pub tls: Option, -} - -impl TryFrom for ServerSettings { - type Error = anyhow::Error; - - fn try_from(value: ServerConfig) -> Result { - Ok(Self { - base_url: value.base_url, - tls: value.tls.map(TryInto::try_into).transpose()?, - }) - } -} - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] -pub struct ExecConfig { - pub provider: Option, - pub model: Option, - pub permissions: Option, - pub output_format: Option, -} - -impl From for ExecSettings { - fn from(value: ExecConfig) -> Self { - Self { - provider: value.provider, - model: value.model, - permissions: value.permissions, - output_format: value.output_format, - } - } -} - -/// Load CLI config from an explicit path or `~/.fabro/cli.toml`, returning defaults if the -/// default file doesn't exist. An explicit path that doesn't exist is an error. -pub fn load_cli_config(path: Option<&Path>) -> anyhow::Result { - crate::load_config_file(path, "cli.toml") -} diff --git a/lib/crates/fabro-config/src/config.rs b/lib/crates/fabro-config/src/config.rs index c66bf5364..381bda5a2 100644 --- a/lib/crates/fabro-config/src/config.rs +++ b/lib/crates/fabro-config/src/config.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; -use crate::cli::{self, ExecConfig, ExecutionMode, ServerConfig}; use crate::combine::Combine; use crate::hook::{HookConfig, HookDefinition}; use crate::mcp::McpServerEntry; @@ -14,6 +13,7 @@ use crate::run::{ use crate::sandbox::SandboxConfig; use crate::server::{self, ApiConfig, Features, GitConfig, LogConfig, WebConfig}; use crate::settings::FabroSettings; +use crate::user::{self, ExecConfig, ExecutionMode, ServerConfig}; fn is_default_checkpoint(c: &CheckpointConfig) -> bool { c.exclude_globs.is_empty() @@ -21,7 +21,7 @@ fn is_default_checkpoint(c: &CheckpointConfig) -> bool { /// Unified sparse configuration type for all Fabro config sources. /// -/// Loading functions (`load_cli_config`, `load_server_config`, `load_run_config`, +/// Loading functions (`load_user_config`, `load_server_config`, `load_run_config`, /// `parse_project_config`) all return this type. Fields irrelevant to a /// particular source are left unset (`None` / empty). #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -76,7 +76,7 @@ pub struct ConfigLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub github: Option, - // --- CLI config fields --- + // --- User config fields --- #[serde(default, skip_serializing_if = "Option::is_none")] pub mode: Option, @@ -222,9 +222,9 @@ impl ConfigLayer { .unwrap_or_default()) } - /// Load CLI defaults from `~/.fabro/cli.toml`. - pub fn cli() -> anyhow::Result { - cli::load_cli_config(None) + /// Load user defaults from `~/.fabro/user.toml`. + pub fn user() -> anyhow::Result { + user::load_user_config(None) } /// Load server defaults from `~/.fabro/server.toml`. diff --git a/lib/crates/fabro-config/src/lib.rs b/lib/crates/fabro-config/src/lib.rs index eb3f9353c..11c1aa350 100644 --- a/lib/crates/fabro-config/src/lib.rs +++ b/lib/crates/fabro-config/src/lib.rs @@ -1,6 +1,5 @@ extern crate self as fabro_config; -pub mod cli; pub mod combine; pub mod config; pub mod dotenv; @@ -11,6 +10,7 @@ pub mod run; pub mod sandbox; pub mod server; pub mod settings; +pub mod user; pub use config::ConfigLayer; pub use fabro_types::Combine; diff --git a/lib/crates/fabro-config/src/user.rs b/lib/crates/fabro-config/src/user.rs new file mode 100644 index 000000000..5d24b3493 --- /dev/null +++ b/lib/crates/fabro-config/src/user.rs @@ -0,0 +1,135 @@ +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::sync::{Mutex, OnceLock}; + +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; + +use crate::config::ConfigLayer; + +pub use fabro_types::settings::user::{ + ClientTlsSettings, ExecSettings, ExecutionMode, OutputFormat, PermissionLevel, ServerSettings, +}; + +pub const USER_CONFIG_FILENAME: &str = "user.toml"; +pub const LEGACY_USER_CONFIG_FILENAME: &str = "cli.toml"; + +static WARNED_LEGACY_USER_CONFIGS: OnceLock>> = OnceLock::new(); + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] +pub struct ClientTlsConfig { + pub cert: Option, + pub key: Option, + pub ca: Option, +} + +impl TryFrom for ClientTlsSettings { + type Error = anyhow::Error; + + fn try_from(value: ClientTlsConfig) -> Result { + Ok(Self { + cert: value.cert.ok_or_else(|| { + anyhow!("server.tls.cert is required when server.tls is configured") + })?, + key: value.key.ok_or_else(|| { + anyhow!("server.tls.key is required when server.tls is configured") + })?, + ca: value.ca.ok_or_else(|| { + anyhow!("server.tls.ca is required when server.tls is configured") + })?, + }) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] +pub struct ServerConfig { + pub base_url: Option, + pub tls: Option, +} + +impl TryFrom for ServerSettings { + type Error = anyhow::Error; + + fn try_from(value: ServerConfig) -> Result { + Ok(Self { + base_url: value.base_url, + tls: value.tls.map(TryInto::try_into).transpose()?, + }) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] +pub struct ExecConfig { + pub provider: Option, + pub model: Option, + pub permissions: Option, + pub output_format: Option, +} + +impl From for ExecSettings { + fn from(value: ExecConfig) -> Self { + Self { + provider: value.provider, + model: value.model, + permissions: value.permissions, + output_format: value.output_format, + } + } +} + +pub fn default_user_config_path() -> Option { + dirs::home_dir().map(|home| home.join(".fabro").join(USER_CONFIG_FILENAME)) +} + +pub fn legacy_user_config_path() -> Option { + dirs::home_dir().map(|home| home.join(".fabro").join(LEGACY_USER_CONFIG_FILENAME)) +} + +fn warned_legacy_user_configs() -> &'static Mutex> { + WARNED_LEGACY_USER_CONFIGS.get_or_init(|| Mutex::new(HashSet::new())) +} + +fn should_warn_about_legacy_user_config(path: &Path) -> bool { + warned_legacy_user_configs() + .lock() + .expect("legacy user config warning lock poisoned") + .insert(path.to_path_buf()) +} + +/// Load user config from an explicit path or `~/.fabro/user.toml`, returning defaults if the +/// default file doesn't exist. An explicit path that doesn't exist is an error. +pub fn load_user_config(path: Option<&Path>) -> anyhow::Result { + if let Some(explicit) = path { + return crate::load_config_file(Some(explicit), USER_CONFIG_FILENAME); + } + + if let Some(legacy_path) = legacy_user_config_path() { + if legacy_path.is_file() && should_warn_about_legacy_user_config(&legacy_path) { + let target = default_user_config_path() + .unwrap_or_else(|| PathBuf::from(format!("~/.fabro/{USER_CONFIG_FILENAME}"))); + eprintln!( + "Warning: ignoring legacy config file {}. Rename it to {}.", + legacy_path.display(), + target.display() + ); + } + } + + crate::load_config_file(None, USER_CONFIG_FILENAME) +} + +#[cfg(test)] +mod tests { + use super::should_warn_about_legacy_user_config; + + #[test] + fn should_warn_about_legacy_user_config_once_per_path() { + let dir = tempfile::tempdir().unwrap(); + let first = dir.path().join("cli.toml"); + let second = dir.path().join("other-cli.toml"); + + assert!(should_warn_about_legacy_user_config(&first)); + assert!(!should_warn_about_legacy_user_config(&first)); + assert!(should_warn_about_legacy_user_config(&second)); + } +} diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index fa6c910b4..b3943d15a 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -3,17 +3,14 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; -pub mod cli; pub mod hook; pub mod mcp; pub mod project; pub mod run; pub mod sandbox; pub mod server; +pub mod user; -pub use cli::{ - ClientTlsSettings, ExecSettings, ExecutionMode, OutputFormat, PermissionLevel, ServerSettings, -}; pub use hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode}; pub use mcp::{ McpServerConfig, McpServerEntry, McpTransport, default_startup_timeout_secs, @@ -35,6 +32,9 @@ pub use server::{ GitProvider, GitSettings, LogSettings, TlsSettings, WebSettings, WebhookSettings, WebhookStrategy, }; +pub use user::{ + ClientTlsSettings, ExecSettings, ExecutionMode, OutputFormat, PermissionLevel, ServerSettings, +}; fn is_default_checkpoint(c: &CheckpointSettings) -> bool { c.exclude_globs.is_empty() diff --git a/lib/crates/fabro-types/src/settings/cli.rs b/lib/crates/fabro-types/src/settings/user.rs similarity index 100% rename from lib/crates/fabro-types/src/settings/cli.rs rename to lib/crates/fabro-types/src/settings/user.rs