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