pub mod capability;
mod defaults;
mod enrichment;
mod env_overrides;
pub mod merge;
mod types;
mod types_policy;
mod validation;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_backend;
pub use capability::{compute_tier, CapabilityTier};
pub use enrichment::{EnrichmentStage, ProfileCompleteness};
pub use merge::{
effective_config, merge_overrides, split_patch_by_scope, validate_override_keys,
EffectiveConfigResult, ACCOUNT_SCOPED_KEYS,
};
pub use types::{
AuthConfig, BusinessProfile, ConnectorConfig, ContentSourceEntry, ContentSourcesConfig,
DeploymentCapabilities, DeploymentMode, EmbeddingConfig, GoogleDriveConnectorConfig,
IntervalsConfig, LimitsConfig, LlmConfig, LoggingConfig, ScoringConfig, ServerConfig,
StorageConfig, TargetsConfig, XApiConfig,
};
pub use types_policy::{CircuitBreakerConfig, McpPolicyConfig, ScheduleConfig};
use crate::error::ConfigError;
use serde::{Deserialize, Serialize};
use std::env;
use std::path::PathBuf;
fn default_approval_mode() -> bool {
true
}
fn default_max_batch_approve() -> usize {
25
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum OperatingMode {
#[default]
Autopilot,
Composer,
}
impl std::fmt::Display for OperatingMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperatingMode::Autopilot => write!(f, "autopilot"),
OperatingMode::Composer => write!(f, "composer"),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Config {
#[serde(default)]
pub mode: OperatingMode,
#[serde(default)]
pub x_api: XApiConfig,
#[serde(default)]
pub auth: AuthConfig,
#[serde(default)]
pub business: BusinessProfile,
#[serde(default)]
pub scoring: ScoringConfig,
#[serde(default)]
pub limits: LimitsConfig,
#[serde(default)]
pub intervals: IntervalsConfig,
#[serde(default)]
pub llm: LlmConfig,
#[serde(default)]
pub targets: TargetsConfig,
#[serde(default = "default_approval_mode")]
pub approval_mode: bool,
#[serde(default = "default_max_batch_approve")]
pub max_batch_approve: usize,
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub storage: StorageConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub schedule: ScheduleConfig,
#[serde(default)]
pub mcp_policy: McpPolicyConfig,
#[serde(default)]
pub circuit_breaker: CircuitBreakerConfig,
#[serde(default)]
pub content_sources: ContentSourcesConfig,
#[serde(default)]
pub deployment_mode: DeploymentMode,
#[serde(default)]
pub connectors: ConnectorConfig,
#[serde(default)]
pub embedding: Option<EmbeddingConfig>,
}
impl Config {
pub fn load(config_path: Option<&str>) -> Result<Config, ConfigError> {
let (path, explicit) = Self::resolve_config_path(config_path);
let mut config = match std::fs::read_to_string(&path) {
Ok(contents) => toml::from_str::<Config>(&contents)
.map_err(|e| ConfigError::ParseError { source: e })?,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if explicit {
return Err(ConfigError::FileNotFound {
path: path.display().to_string(),
});
}
Config::default()
}
Err(_) => {
return Err(ConfigError::FileNotFound {
path: path.display().to_string(),
});
}
};
config.apply_env_overrides()?;
Ok(config)
}
pub fn load_and_validate(config_path: Option<&str>) -> Result<Config, Vec<ConfigError>> {
let config = Config::load(config_path).map_err(|e| vec![e])?;
config.validate()?;
Ok(config)
}
pub fn effective_approval_mode(&self) -> bool {
self.approval_mode || self.mode == OperatingMode::Composer
}
pub fn is_composer_mode(&self) -> bool {
self.mode == OperatingMode::Composer
}
fn resolve_config_path(config_path: Option<&str>) -> (PathBuf, bool) {
if let Some(path) = config_path {
return (expand_tilde(path), true);
}
if let Ok(env_path) = env::var("TUITBOT_CONFIG") {
return (expand_tilde(&env_path), true);
}
(expand_tilde("~/.tuitbot/config.toml"), false)
}
}
fn expand_tilde(path: &str) -> PathBuf {
if let Some(rest) = path.strip_prefix("~/") {
if let Some(home) = dirs::home_dir() {
return home.join(rest);
}
} else if path == "~" {
if let Some(home) = dirs::home_dir() {
return home;
}
}
PathBuf::from(path)
}