use clap::Args;
use octomind::config::{Config, McpConnectionType, McpServerConfig};
use octomind::directories;
#[derive(Args)]
pub struct ConfigArgs {
#[arg(long)]
pub model: Option<String>,
#[arg(long)]
pub api_key: Option<String>,
#[arg(long)]
pub log_level: Option<String>,
#[arg(long)]
pub mcp_providers: Option<String>,
#[arg(long)]
pub mcp_server: Option<String>,
#[arg(long)]
pub system: Option<String>,
#[arg(long)]
pub markdown_enable: Option<bool>,
#[arg(long)]
pub markdown_theme: Option<String>,
#[arg(long)]
pub list_themes: bool,
#[arg(long)]
pub show: bool,
#[arg(long)]
pub validate: bool,
#[arg(long)]
pub upgrade: bool,
}
pub fn execute(args: &ConfigArgs, mut config: Config) -> Result<(), anyhow::Error> {
if args.list_themes {
list_markdown_themes();
return Ok(());
}
if args.show {
show_configuration(&config)?;
return Ok(());
}
if args.validate {
match config.validate() {
Ok(()) => {
println!("✅ Configuration is valid!");
return Ok(());
}
Err(e) => {
octomind::log_error!("❌ Configuration validation failed: {}", e);
return Err(e);
}
}
}
if args.upgrade {
let config_path = directories::get_config_file_path()?;
octomind::config::migrations::force_upgrade_config(&config_path)?;
return Ok(());
}
let mut modified = false;
if let Some(model) = &args.model {
if !model.contains(':') {
octomind::log_error!("Error: Model must be in provider:model format (e.g., openrouter:anthropic/claude-3.5-sonnet)");
return Ok(());
}
config.model = model.clone();
println!("Set root-level model to {}", model);
modified = true;
}
if let Some(api_key_input) = &args.api_key {
let parts: Vec<&str> = api_key_input.splitn(2, ':').collect();
if parts.len() != 2 {
octomind::log_error!(
"Error: API key must be in provider:key format (e.g., openrouter:your-key)"
);
return Ok(());
}
let provider = parts[0];
octomind::log_error!(
"❌ Error: API keys can no longer be set in config file for security reasons."
);
octomind::log_error!("Please set the API key as an environment variable instead:");
octomind::log_error!(
" For {}: export {}_API_KEY=your-key-here",
provider.to_uppercase(),
provider.to_uppercase()
);
octomind::log_error!(" Then restart your shell and try again.");
return Ok(());
}
if let Some(log_level_str) = &args.log_level {
match log_level_str.to_lowercase().as_str() {
"none" => {
config.log_level = octomind::config::LogLevel::None;
println!("Set log level to None");
}
"info" => {
config.log_level = octomind::config::LogLevel::Info;
println!("Set log level to Info");
}
"debug" => {
config.log_level = octomind::config::LogLevel::Debug;
println!("Set log level to Debug");
}
_ => {
octomind::log_error!(
"Error: Invalid log level '{}'. Valid options: none, info, debug",
log_level_str
);
return Ok(());
}
}
modified = true;
}
if let Some(enable_markdown) = args.markdown_enable {
config.enable_markdown_rendering = enable_markdown;
println!(
"Markdown rendering {}",
if enable_markdown {
"enabled"
} else {
"disabled"
}
);
modified = true;
}
if let Some(theme) = &args.markdown_theme {
let valid_themes = octomind::session::chat::markdown::MarkdownTheme::all_themes();
if valid_themes.contains(&theme.as_str()) {
config.markdown_theme = theme.clone();
println!("Markdown theme set to '{}'", theme);
modified = true;
} else {
octomind::log_error!(
"Error: Invalid markdown theme '{}'. Valid themes: {}",
theme,
valid_themes.join(", ")
);
return Ok(());
}
}
if let Some(providers) = &args.mcp_providers {
let server_names: Vec<String> =
providers.split(',').map(|s| s.trim().to_string()).collect();
config.mcp.servers.clear();
for server_name in &server_names {
if !config.mcp.servers.iter().any(|s| s.name() == *server_name) {
let server = McpServerConfig::builtin(server_name, 30, Vec::new());
config.mcp.servers.push(server);
}
}
println!("Set MCP servers to: {}", providers);
modified = true;
}
if let Some(server_config) = &args.mcp_server {
let parts: Vec<&str> = server_config.split(',').collect();
if parts.len() < 2 {
println!("Invalid MCP server configuration format. Expected format: name,url=X|command=Y,args=Z");
} else {
let name = parts[0].trim().to_string();
let mut url: Option<String> = None;
let mut command: Option<String> = None;
let mut args: Vec<String> = Vec::new();
let mut timeout_seconds: u64 = 30;
let mut connection_type = McpConnectionType::Http;
for part in &parts[1..] {
let kv: Vec<&str> = part.split('=').collect();
if kv.len() == 2 {
let key = kv[0].trim();
let value = kv[1].trim();
match key {
"url" => {
url = Some(value.to_string());
}
"command" => {
command = Some(value.to_string());
}
"args" => {
args = value
.split(' ')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
"type" => match value.to_lowercase().as_str() {
"http" => connection_type = McpConnectionType::Http,
"stdio" => connection_type = McpConnectionType::Stdin,
"builtin" => connection_type = McpConnectionType::Builtin,
_ => println!("Unknown server type: {}, defaulting to HTTP", value),
},
"timeout" | "timeout_seconds" => {
if let Ok(timeout) = value.parse::<u64>() {
timeout_seconds = timeout;
} else {
println!("Invalid timeout value: {}, using default", value);
}
}
_ => {
println!("Unknown server config key: {}", key);
}
}
}
}
let server = match connection_type {
McpConnectionType::Builtin => {
McpServerConfig::builtin(&name, timeout_seconds, Vec::new())
}
McpConnectionType::Http => {
if let Some(url) = url {
McpServerConfig::http(&name, &url, timeout_seconds, Vec::new())
} else {
println!("Error: URL must be specified for HTTP MCP server");
return Ok(());
}
}
McpConnectionType::Stdin => {
if let Some(command) = command {
McpServerConfig::stdin(&name, &command, args, timeout_seconds, Vec::new())
} else {
println!("Error: Command must be specified for stdin MCP server");
return Ok(());
}
}
};
config.mcp.servers.retain(|s| s.name() != name);
config.mcp.servers.push(server);
println!("Added/updated MCP server: {}", name);
modified = true;
}
}
if let Some(system_prompt) = &args.system {
if system_prompt.to_lowercase() == "default" {
config.system = None;
println!("Reset system prompt to default");
} else {
config.system = Some(system_prompt.clone());
println!("Set custom system prompt");
}
modified = true;
}
if !modified {
let config_path = directories::get_config_file_path()?;
if config_path.exists() {
println!(
"Configuration file already exists at: {}",
config_path.display()
);
println!("No changes were made to the configuration.");
} else {
let config_path = Config::create_default_config()?;
println!(
"Created default configuration file at: {}",
config_path.display()
);
}
} else {
if let Err(e) = config.save() {
octomind::log_error!("Error saving configuration: {}", e);
return Err(e);
}
println!("Configuration saved successfully");
}
println!("\nCurrent configuration:");
println!("Root model: {}", config.get_effective_model());
println!("Provider API keys (from environment variables):");
show_env_api_key_status(" OpenRouter", "OPENROUTER_API_KEY");
show_env_api_key_status(" OpenAI", "OPENAI_API_KEY");
show_env_api_key_status(" Anthropic", "ANTHROPIC_API_KEY");
show_env_api_key_status(" Google", "GOOGLE_APPLICATION_CREDENTIALS");
show_env_api_key_status(" Amazon", "AWS_ACCESS_KEY_ID");
show_env_api_key_status(" Cloudflare", "CLOUDFLARE_API_TOKEN");
println!("Role configurations:");
let dev_mcp_enabled = config
.role_map
.get("developer")
.map(|r| !r.mcp.server_refs.is_empty())
.unwrap_or(false);
let ass_mcp_enabled = config
.role_map
.get("assistant")
.map(|r| !r.mcp.server_refs.is_empty())
.unwrap_or(false);
println!("MCP status:");
println!(
" Developer role: {}",
if dev_mcp_enabled {
"enabled"
} else {
"disabled"
}
);
println!(
" Assistant role: {}",
if ass_mcp_enabled {
"enabled"
} else {
"disabled"
}
);
if !config.mcp.servers.is_empty() || dev_mcp_enabled || ass_mcp_enabled {
if !config.mcp.servers.is_empty() {
println!("MCP servers:");
for server in &config.mcp.servers {
let name = server.name();
let effective_type = match name {
"core" | "agent" => McpConnectionType::Builtin,
_ => server.connection_type(),
};
match effective_type {
McpConnectionType::Builtin => match name {
"core" => {
println!(" - {} (built-in core tools) - available", name)
}
"agent" => println!(" - {} (built-in agent tool) - available", name),
_ => println!(" - {} (built-in tools) - available", name),
},
McpConnectionType::Http | McpConnectionType::Stdin => {
if let Some(url) = server.url() {
println!(" - {} (HTTP: {}) - available", name, url);
} else if let Some(command) = server.command() {
println!(" - {} (Command: {}) - available", name, command);
} else {
println!(
" - {} (external, not configured) - needs configuration",
name
);
}
}
}
}
} else {
println!("MCP servers: None configured");
}
}
println!("Log level: {:?}", config.log_level);
println!(
"Markdown rendering: {}",
if config.enable_markdown_rendering {
"enabled"
} else {
"disabled"
}
);
println!("Markdown theme: {}", config.markdown_theme);
if config.system.is_some() {
println!("System prompt: Custom");
} else {
println!("System prompt: Default");
}
Ok(())
}
fn list_markdown_themes() {
println!("🎨 Available Markdown Themes\n");
let themes = vec![
(
"default",
"Improved default theme with gold headers and enhanced contrast",
"Most terminal setups",
),
(
"dark",
"Optimized for dark terminals with bright, vibrant colors",
"Dark terminal backgrounds",
),
(
"light",
"Perfect for light terminal backgrounds with darker colors",
"Light terminal backgrounds",
),
(
"ocean",
"Beautiful blue-green palette inspired by ocean themes",
"Users who prefer calm, aquatic colors",
),
(
"solarized",
"Based on the popular Solarized color scheme",
"Fans of the classic Solarized palette",
),
(
"monokai",
"Inspired by the popular Monokai syntax highlighting theme",
"Users familiar with Monokai from code editors",
),
];
for (name, description, best_for) in themes {
println!("📝 {}", name.to_uppercase());
println!(" Description: {}", description);
println!(" Best for: {}", best_for);
println!(" Usage: octomind config --markdown-theme {}", name);
println!();
}
println!("💡 Tips:");
println!(" • Themes work in sessions, ask command, and multimode");
println!(" • Enable markdown rendering: octomind config --markdown-enable true");
println!(" • View current theme: octomind config --show");
}
fn show_configuration(config: &Config) -> Result<(), anyhow::Error> {
println!("🔧 Octomind Configuration\n");
let config_path = directories::get_config_file_path()?;
if config_path.exists() {
println!("📁 Config file: {}", config_path.display());
} else {
println!(
"📁 Config file: {} (not created yet)",
config_path.display()
);
}
println!();
println!("🌍 System-wide Settings");
println!(
" Model (root): {}",
if config.model.is_empty() || config.model == "openrouter:anthropic/claude-3.5-haiku" {
format!("{} (default)", config.get_effective_model())
} else {
config.model.clone()
}
);
println!(" Log level: {:?}", config.log_level);
println!(
" Markdown rendering: {}",
if config.enable_markdown_rendering {
"enabled"
} else {
"disabled"
}
);
println!(" Markdown theme: {}", config.markdown_theme);
println!(
" MCP response warning: {} tokens",
config.mcp_response_warning_threshold
);
println!(
" Max session tokens: {} tokens ({})",
config.max_session_tokens_threshold,
if config.max_session_tokens_threshold > 0 {
"enabled"
} else {
"disabled"
}
);
println!(
" Cache threshold: {} tokens",
config.cache_tokens_threshold
);
println!(
" Cache timeout: {} seconds",
config.cache_timeout_seconds
);
println!();
println!("🔑 Provider API Keys (from environment variables)");
show_env_api_key_status("OpenRouter", "OPENROUTER_API_KEY");
show_env_api_key_status("OpenAI", "OPENAI_API_KEY");
show_env_api_key_status("Anthropic", "ANTHROPIC_API_KEY");
show_env_api_key_status("Google", "GOOGLE_APPLICATION_CREDENTIALS");
show_env_api_key_status("Amazon", "AWS_ACCESS_KEY_ID");
show_env_api_key_status("Cloudflare", "CLOUDFLARE_API_TOKEN");
println!();
println!("👤 Role Configurations");
println!(" Developer Role:");
let (_dev_config, dev_mcp, _dev_layers, _dev_commands, dev_system) =
config.get_role_config("developer");
println!(
" Model: {} (system-wide)",
config.get_effective_model()
);
println!(" System prompt: {} chars", dev_system.len());
println!(" Assistant Role:");
let (_ass_config, ass_mcp, _ass_layers, _ass_commands, ass_system) =
config.get_role_config("assistant");
println!(
" Model: {} (system-wide)",
config.get_effective_model()
);
println!(" System prompt: {} chars", ass_system.len());
println!();
println!("🔧 MCP (Model Context Protocol) Configuration");
println!(" Global MCP:");
println!(
" Registry: {} servers configured",
config.mcp.servers.len()
);
if !config.mcp.servers.is_empty() {
show_mcp_servers(&config.mcp.servers);
}
println!(" Developer Role MCP:");
println!(
" Server refs: {}",
if dev_mcp.server_refs.is_empty() {
"None (MCP disabled)".to_string()
} else {
dev_mcp.server_refs.join(", ")
}
);
println!(" Assistant Role MCP:");
println!(
" Server refs: {}",
if ass_mcp.server_refs.is_empty() {
"None (MCP disabled)".to_string()
} else {
ass_mcp.server_refs.join(", ")
}
);
println!();
let has_any_workflow = config.role_map.values().any(|r| r.workflow.is_some());
if has_any_workflow {
println!("📚 Layer Configurations (used by workflows)");
if let Some(layers) = &config.layers {
println!(" Configured Layers: {} available", layers.len());
for layer in layers {
println!(" ✅ {} (temp: {:.1})", layer.name, layer.temperature);
}
}
println!("\n Workflow Assignments:");
for (role_name, role_data) in &config.role_map {
if let Some(workflow) = &role_data.workflow {
println!(" {} → {}", role_name, workflow);
}
}
println!();
}
Ok(())
}
fn show_env_api_key_status(provider: &str, env_var: &str) {
match std::env::var(env_var) {
Ok(value) if !value.trim().is_empty() => {
let tracker = octomind::config::get_env_tracker();
let source_desc = tracker.lock().unwrap().get_source_description(env_var);
println!("{:<15} ✅ Set via {}", provider, source_desc);
}
_ => {
println!(
"{:<15} ❌ Not set (export {}=your-key or add to .env)",
provider, env_var
);
}
}
}
fn show_mcp_servers(servers: &[McpServerConfig]) {
if servers.is_empty() {
println!(" Servers: None configured");
return;
}
println!(" Servers:");
for server in servers {
let name = server.name();
let effective_type = match name {
"core" | "agent" => McpConnectionType::Builtin,
_ => server.connection_type(),
};
match effective_type {
McpConnectionType::Builtin => match name {
"core" => println!(" 📦 {} (built-in core tools)", name),
"agent" => println!(" 🤖 {} (built-in agent tool)", name),
_ => println!(" 📦 {} (built-in tools)", name),
},
McpConnectionType::Http | McpConnectionType::Stdin => {
if let Some(url) = server.url() {
println!(" 🌐 {} (HTTP: {})", name, url);
} else if let Some(command) = server.command() {
println!(" ⚙️ {} (Command: {})", name, command);
} else {
println!(" ❓ {} (external, not configured)", name);
}
}
}
if server.timeout_seconds() != 30 {
println!(" Timeout: {} seconds", server.timeout_seconds());
}
if !server.tools().is_empty() {
println!(" Tools: {}", server.tools().join(", "));
}
}
}