(config_path: &str)
| 672 | } |
| 673 | |
| 674 | async fn cmd_start_inner(config_path: &str) -> Result<(), Box<dyn std::error::Error>> { |
| 675 | // Validate configuration |
| 676 | let path = PathBuf::from(config_path); |
| 677 | ensure_config_migrated(&path)?; |
| 678 | let config = Config::from_file(&path) |
| 679 | .map_err(|e| format!("Failed to load configuration from '{}': {}", config_path, e))?; |
| 680 | |
| 681 | // Build OAuth registry if any OAuth providers are configured |
| 682 | let oauth_registry = build_oauth_registry(&config).await?; |
| 683 | let app_state = AppState::from_config_with_registry(&config, Some(oauth_registry)) |
| 684 | .map_err(|e| format!("Failed to initialize app state: {e}"))?; |
| 685 | |
| 686 | tui::print_success("Configuration validated successfully."); |
| 687 | |
| 688 | // Ensure runtime directories exist (for log files) |
| 689 | process::ensure_runtime_dirs()?; |
| 690 | |
| 691 | let env_filter: tracing_subscriber::EnvFilter = config |
| 692 | .log_level |
| 693 | .parse() |
| 694 | .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); |
| 695 | tracing_subscriber::fmt() |
| 696 | .with_env_filter(env_filter) |
| 697 | .with_target(true) |
| 698 | .init(); |
| 699 | |
| 700 | let listen_addr = config |
| 701 | .resolved_listen_addr() |
| 702 | .map_err(|e| format!("invalid server environment override: {e}"))?; |
| 703 | |
| 704 | info!( |
| 705 | listen = %listen_addr, |
| 706 | upstream = config.upstream.openai_base_url, |
| 707 | keys = config.keys.len(), |
| 708 | oauth_providers = config.oauth_providers.len(), |
| 709 | "ClawShell starting" |
| 710 | ); |
| 711 | debug!( |
| 712 | dlp_patterns = config.dlp.patterns.len(), |
| 713 | scan_responses = config.dlp.scan_responses, |
| 714 | log_level = %config.log_level, |
| 715 | "Configuration loaded" |
| 716 | ); |
| 717 | |
| 718 | // Spawn background OAuth refresh tasks |
| 719 | let cancel = tokio_util::sync::CancellationToken::new(); |
| 720 | app_state.oauth_registry.spawn_refresh_tasks(cancel.clone()); |
| 721 | |
| 722 | // Periodically persist stats counters so they survive restarts. |
| 723 | let stats_for_task = app_state.stats.clone(); |
| 724 | let stats_cancel = cancel.clone(); |
| 725 | tokio::spawn(async move { |
| 726 | let interval = std::time::Duration::from_secs(30); |
| 727 | loop { |
| 728 | tokio::select! { |
| 729 | _ = stats_cancel.cancelled() => break, |
| 730 | _ = tokio::time::sleep(interval) => { |
| 731 | if let Err(err) = stats_for_task.persist() { |
no test coverage detected