mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Rename cli.toml to user.toml
This commit is contained in:
parent
dfc30b6d3a
commit
be6e37fa26
58 changed files with 434 additions and 255 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
"pages": [
|
||||
"reference/dot-language",
|
||||
"reference/cli",
|
||||
"reference/cli-configuration",
|
||||
"reference/user-configuration",
|
||||
"reference/run-directory",
|
||||
"reference/sdk",
|
||||
"reference/architecture",
|
||||
|
|
|
|||
|
|
@ -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 <DIR>` | Storage directory for local run data (default: `~/.fabro`). Implies standalone mode. |
|
||||
| `--server-url <URL>` | Fabro API server URL (overrides `server.base_url` from `cli.toml`). Implies server mode. |
|
||||
| `--server-url <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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub(crate) struct GlobalArgs {
|
|||
pub storage_dir: Option<PathBuf>,
|
||||
|
||||
#[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,
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>) -> CheckResult {
|
||||
match path {
|
||||
Some(p) => CheckResult {
|
||||
pub(crate) fn check_config(
|
||||
user_path: Option<PathBuf>,
|
||||
legacy_path: Option<PathBuf>,
|
||||
) -> 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]
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ static RANKDIR_RE: LazyLock<regex::Regex> =
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ModelsCommand>, 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"))]
|
||||
|
|
|
|||
|
|
@ -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<fabro_github::GitHubAppCredentials>,
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<fabro_github::GitHubAppCredentials>,
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<fabro_github::GitHubAppCredentials>,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<fabro_github::GitHubAppCredentials>,
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<fabro_github::GitHubAppCredentials>,
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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<PathBuf> {
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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}"));
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<FabroSettings> {
|
||||
ConfigLayer::cli()?.resolve()
|
||||
pub(crate) fn load_user_settings() -> anyhow::Result<FabroSettings> {
|
||||
ConfigLayer::user()?.resolve()
|
||||
}
|
||||
|
||||
pub(crate) fn cli_layer_with_globals(globals: &GlobalArgs) -> anyhow::Result<ConfigLayer> {
|
||||
let layer = ConfigLayer::cli()?;
|
||||
pub(crate) fn user_layer_with_globals(globals: &GlobalArgs) -> anyhow::Result<ConfigLayer> {
|
||||
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<FabroSettings> {
|
||||
cli_layer_with_globals(globals)?.resolve()
|
||||
user_layer_with_globals(globals)?.resolve()
|
||||
}
|
||||
|
||||
pub(crate) fn apply_global_overrides(mut layer: ConfigLayer, globals: &GlobalArgs) -> ConfigLayer {
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Port to listen on [default: 3000]
|
||||
--host <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 <MODEL>
|
||||
Override default LLM model
|
||||
--quiet
|
||||
Suppress non-essential output
|
||||
Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--provider <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_DIR>
|
||||
Storage directory (default: ~/.fabro)
|
||||
Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=]
|
||||
--sandbox <SANDBOX>
|
||||
Sandbox for agent tools
|
||||
--server-url <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 <MAX_CONCURRENT_RUNS>
|
||||
Maximum number of concurrent run executions
|
||||
--config <CONFIG>
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
pub key: Option<PathBuf>,
|
||||
pub ca: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TryFrom<ClientTlsConfig> for ClientTlsSettings {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ClientTlsConfig) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub tls: Option<ClientTlsConfig>,
|
||||
}
|
||||
|
||||
impl TryFrom<ServerConfig> for ServerSettings {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ServerConfig) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub model: Option<String>,
|
||||
pub permissions: Option<PermissionLevel>,
|
||||
pub output_format: Option<OutputFormat>,
|
||||
}
|
||||
|
||||
impl From<ExecConfig> 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<ConfigLayer> {
|
||||
crate::load_config_file(path, "cli.toml")
|
||||
}
|
||||
|
|
@ -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<GitHubConfig>,
|
||||
|
||||
// --- CLI config fields ---
|
||||
// --- User config fields ---
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<ExecutionMode>,
|
||||
|
||||
|
|
@ -222,9 +222,9 @@ impl ConfigLayer {
|
|||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Load CLI defaults from `~/.fabro/cli.toml`.
|
||||
pub fn cli() -> anyhow::Result<Self> {
|
||||
cli::load_cli_config(None)
|
||||
/// Load user defaults from `~/.fabro/user.toml`.
|
||||
pub fn user() -> anyhow::Result<Self> {
|
||||
user::load_user_config(None)
|
||||
}
|
||||
|
||||
/// Load server defaults from `~/.fabro/server.toml`.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
135
lib/crates/fabro-config/src/user.rs
Normal file
135
lib/crates/fabro-config/src/user.rs
Normal file
|
|
@ -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<Mutex<HashSet<PathBuf>>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)]
|
||||
pub struct ClientTlsConfig {
|
||||
pub cert: Option<PathBuf>,
|
||||
pub key: Option<PathBuf>,
|
||||
pub ca: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TryFrom<ClientTlsConfig> for ClientTlsSettings {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ClientTlsConfig) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub tls: Option<ClientTlsConfig>,
|
||||
}
|
||||
|
||||
impl TryFrom<ServerConfig> for ServerSettings {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ServerConfig) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub model: Option<String>,
|
||||
pub permissions: Option<PermissionLevel>,
|
||||
pub output_format: Option<OutputFormat>,
|
||||
}
|
||||
|
||||
impl From<ExecConfig> 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<PathBuf> {
|
||||
dirs::home_dir().map(|home| home.join(".fabro").join(USER_CONFIG_FILENAME))
|
||||
}
|
||||
|
||||
pub fn legacy_user_config_path() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|home| home.join(".fabro").join(LEGACY_USER_CONFIG_FILENAME))
|
||||
}
|
||||
|
||||
fn warned_legacy_user_configs() -> &'static Mutex<HashSet<PathBuf>> {
|
||||
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<ConfigLayer> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue