mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-29 08:59:31 +00:00
- Add 7-step setup wizard: database, security, auth, model, embeddings, channels, heartbeat - Store settings in ~/.ironclaw/settings.json with env var > settings > default priority - Add OS keychain integration for secrets master key (macOS/Linux) - Add `ironclaw config` CLI subcommand (list/get/set/reset/path) - Expand Settings struct with all configuration fields - Enhanced setup detection to auto-trigger wizard when needed Co-Authored-By: Claude Opus 4.5 <[email protected]>
196 lines
5.0 KiB
Rust
196 lines
5.0 KiB
Rust
//! Configuration management CLI commands.
|
|
//!
|
|
//! Commands for viewing and modifying settings.
|
|
|
|
use clap::Subcommand;
|
|
|
|
use crate::settings::Settings;
|
|
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
pub enum ConfigCommand {
|
|
/// List all settings and their current values
|
|
List {
|
|
/// Show only settings matching this prefix (e.g., "agent", "heartbeat")
|
|
#[arg(short, long)]
|
|
filter: Option<String>,
|
|
},
|
|
|
|
/// Get a specific setting value
|
|
Get {
|
|
/// Setting path (e.g., "agent.max_parallel_jobs")
|
|
path: String,
|
|
},
|
|
|
|
/// Set a setting value
|
|
Set {
|
|
/// Setting path (e.g., "agent.max_parallel_jobs")
|
|
path: String,
|
|
|
|
/// Value to set
|
|
value: String,
|
|
},
|
|
|
|
/// Reset a setting to its default value
|
|
Reset {
|
|
/// Setting path (e.g., "agent.max_parallel_jobs")
|
|
path: String,
|
|
},
|
|
|
|
/// Show the settings file path
|
|
Path,
|
|
}
|
|
|
|
/// Run a config command.
|
|
pub fn run_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
|
match cmd {
|
|
ConfigCommand::List { filter } => list_settings(filter),
|
|
ConfigCommand::Get { path } => get_setting(&path),
|
|
ConfigCommand::Set { path, value } => set_setting(&path, &value),
|
|
ConfigCommand::Reset { path } => reset_setting(&path),
|
|
ConfigCommand::Path => show_path(),
|
|
}
|
|
}
|
|
|
|
/// List all settings.
|
|
fn list_settings(filter: Option<String>) -> anyhow::Result<()> {
|
|
let settings = Settings::load();
|
|
let all = settings.list();
|
|
|
|
// Find the longest key for alignment
|
|
let max_key_len = all.iter().map(|(k, _)| k.len()).max().unwrap_or(0);
|
|
|
|
println!("Settings:");
|
|
println!();
|
|
|
|
for (key, value) in all {
|
|
// Skip if filter is set and doesn't match
|
|
if let Some(ref f) = filter {
|
|
if !key.starts_with(f) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Truncate long values for display
|
|
let display_value = if value.len() > 60 {
|
|
format!("{}...", &value[..57])
|
|
} else {
|
|
value
|
|
};
|
|
|
|
println!(" {:width$} {}", key, display_value, width = max_key_len);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get a specific setting.
|
|
fn get_setting(path: &str) -> anyhow::Result<()> {
|
|
let settings = Settings::load();
|
|
|
|
match settings.get(path) {
|
|
Some(value) => {
|
|
println!("{}", value);
|
|
Ok(())
|
|
}
|
|
None => {
|
|
anyhow::bail!("Setting not found: {}", path);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set a setting value.
|
|
fn set_setting(path: &str, value: &str) -> anyhow::Result<()> {
|
|
let mut settings = Settings::load();
|
|
|
|
// Try to set the value
|
|
settings
|
|
.set(path, value)
|
|
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
|
|
|
// Save to disk
|
|
settings.save()?;
|
|
|
|
println!("Set {} = {}", path, value);
|
|
Ok(())
|
|
}
|
|
|
|
/// Reset a setting to default.
|
|
fn reset_setting(path: &str) -> anyhow::Result<()> {
|
|
let mut settings = Settings::load();
|
|
|
|
// Get the default value for display
|
|
let default = Settings::default();
|
|
let default_value = default
|
|
.get(path)
|
|
.ok_or_else(|| anyhow::anyhow!("Unknown setting: {}", path))?;
|
|
|
|
// Reset it
|
|
settings.reset(path).map_err(|e| anyhow::anyhow!("{}", e))?;
|
|
|
|
// Save to disk
|
|
settings.save()?;
|
|
|
|
println!("Reset {} to default: {}", path, default_value);
|
|
Ok(())
|
|
}
|
|
|
|
/// Show the settings file path.
|
|
fn show_path() -> anyhow::Result<()> {
|
|
let path = Settings::default_path();
|
|
println!("{}", path.display());
|
|
|
|
if path.exists() {
|
|
let metadata = std::fs::metadata(&path)?;
|
|
println!(" Size: {} bytes", metadata.len());
|
|
if let Ok(modified) = metadata.modified() {
|
|
use std::time::SystemTime;
|
|
let duration = SystemTime::now()
|
|
.duration_since(modified)
|
|
.unwrap_or_default();
|
|
let secs = duration.as_secs();
|
|
if secs < 60 {
|
|
println!(" Modified: {} seconds ago", secs);
|
|
} else if secs < 3600 {
|
|
println!(" Modified: {} minutes ago", secs / 60);
|
|
} else if secs < 86400 {
|
|
println!(" Modified: {} hours ago", secs / 3600);
|
|
} else {
|
|
println!(" Modified: {} days ago", secs / 86400);
|
|
}
|
|
}
|
|
} else {
|
|
println!(" (does not exist, using defaults)");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::tempdir;
|
|
|
|
#[test]
|
|
fn test_list_settings() {
|
|
// Just verify it doesn't panic
|
|
let settings = Settings::default();
|
|
let list = settings.list();
|
|
assert!(!list.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_set_reset() {
|
|
let _dir = tempdir().unwrap();
|
|
|
|
let mut settings = Settings::default();
|
|
|
|
// Set a value
|
|
settings.set("agent.name", "testbot").unwrap();
|
|
assert_eq!(settings.agent.name, "testbot");
|
|
|
|
// Reset to default
|
|
settings.reset("agent.name").unwrap();
|
|
assert_eq!(settings.agent.name, "ironclaw");
|
|
}
|
|
}
|