| use std::hash::{DefaultHasher, Hash, Hasher}; |
| use std::path::PathBuf; |
|
|
| use derive_more::Display; |
| use derive_setters::Setters; |
| use serde::{Deserialize, Serialize}; |
|
|
| use crate::{Effort, ModelConfig}; |
|
|
| |
| |
| |
| |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum ConfigOperation { |
| |
| |
| |
| |
| |
| SetSessionConfig(ModelConfig), |
| |
| |
| |
| |
| SetCommitConfig(Option<ModelConfig>), |
| |
| SetSuggestConfig(ModelConfig), |
| |
| SetReasoningEffort(Effort), |
| } |
|
|
| const VERSION: &str = match option_env!("APP_VERSION") { |
| Some(val) => val, |
| None => env!("CARGO_PKG_VERSION"), |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| #[derive(Debug, Setters, Clone, PartialEq, Serialize, Deserialize, fake::Dummy)] |
| #[serde(rename_all = "camelCase")] |
| #[setters(strip_option)] |
| pub struct Environment { |
| |
| pub os: String, |
| |
| pub cwd: PathBuf, |
| |
| pub home: Option<PathBuf>, |
| |
| pub shell: String, |
| |
| pub base_path: PathBuf, |
| } |
|
|
| impl Environment { |
| pub fn log_path(&self) -> PathBuf { |
| self.base_path.join("logs") |
| } |
|
|
| |
| |
| |
| |
| |
| |
| pub fn history_path(&self, custom_path: Option<&PathBuf>) -> PathBuf { |
| custom_path |
| .cloned() |
| .unwrap_or(self.base_path.join(".forge_history")) |
| } |
| pub fn snapshot_path(&self) -> PathBuf { |
| self.base_path.join("snapshots") |
| } |
| pub fn mcp_user_config(&self) -> PathBuf { |
| self.base_path.join(".mcp.json") |
| } |
|
|
| |
| pub fn mcp_trust_path(&self) -> PathBuf { |
| self.base_path.join(".mcp_trust.json") |
| } |
|
|
| pub fn agent_path(&self) -> PathBuf { |
| self.base_path.join("agents") |
| } |
| pub fn agent_cwd_path(&self) -> PathBuf { |
| self.cwd.join(".forge/agents") |
| } |
|
|
| pub fn permissions_path(&self) -> PathBuf { |
| self.base_path.join("permissions.yaml") |
| } |
|
|
| pub fn mcp_local_config(&self) -> PathBuf { |
| self.cwd.join(".mcp.json") |
| } |
|
|
| pub fn version(&self) -> String { |
| VERSION.to_string() |
| } |
|
|
| pub fn app_config(&self) -> PathBuf { |
| self.base_path.join(".config.json") |
| } |
|
|
| pub fn database_path(&self) -> PathBuf { |
| self.base_path.join(".forge.db") |
| } |
|
|
| |
| pub fn cache_dir(&self) -> PathBuf { |
| self.base_path.join("cache") |
| } |
|
|
| |
| pub fn global_skills_path(&self) -> PathBuf { |
| self.base_path.join("skills") |
| } |
|
|
| |
| |
| |
| pub fn agents_skills_path(&self) -> Option<PathBuf> { |
| self.home.as_ref().map(|home| home.join(".agents/skills")) |
| } |
|
|
| |
| pub fn local_skills_path(&self) -> PathBuf { |
| self.cwd.join(".forge/skills") |
| } |
|
|
| |
| pub fn command_path(&self) -> PathBuf { |
| self.base_path.join("commands") |
| } |
|
|
| |
| pub fn command_path_local(&self) -> PathBuf { |
| self.cwd.join(".forge/commands") |
| } |
|
|
| |
| pub fn global_agentsmd_path(&self) -> PathBuf { |
| self.base_path.join("AGENTS.md") |
| } |
|
|
| |
| pub fn local_agentsmd_path(&self) -> PathBuf { |
| self.cwd.join("AGENTS.md") |
| } |
|
|
| |
| |
| pub fn plans_path(&self) -> PathBuf { |
| self.cwd.join("plans") |
| } |
|
|
| |
| |
| pub fn provider_config_path(&self) -> PathBuf { |
| self.base_path.join("provider.json") |
| } |
|
|
| |
| |
| pub fn credentials_path(&self) -> PathBuf { |
| self.base_path.join(".credentials.json") |
| } |
|
|
| pub fn workspace_hash(&self) -> WorkspaceHash { |
| let mut hasher = DefaultHasher::default(); |
| self.cwd.hash(&mut hasher); |
|
|
| WorkspaceHash(hasher.finish()) |
| } |
| } |
|
|
| #[derive(Clone, Copy, Display)] |
| pub struct WorkspaceHash(u64); |
| impl WorkspaceHash { |
| pub fn new(id: u64) -> Self { |
| WorkspaceHash(id) |
| } |
|
|
| pub fn id(&self) -> u64 { |
| self.0 |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use fake::{Fake, Faker}; |
| use pretty_assertions::assert_eq; |
|
|
| use super::*; |
|
|
| #[test] |
| fn test_agent_cwd_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.cwd(PathBuf::from("/current/working/dir")); |
|
|
| let actual = fixture.agent_cwd_path(); |
| let expected = PathBuf::from("/current/working/dir/.forge/agents"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_agent_cwd_path_independent_from_agent_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture |
| .cwd(PathBuf::from("/different/current/dir")) |
| .base_path(PathBuf::from("/completely/different/base")); |
|
|
| let agent_path = fixture.agent_path(); |
| let agent_cwd_path = fixture.agent_cwd_path(); |
| let expected_agent_path = PathBuf::from("/completely/different/base/agents"); |
| let expected_agent_cwd_path = PathBuf::from("/different/current/dir/.forge/agents"); |
|
|
| |
| assert_eq!(agent_path, expected_agent_path); |
|
|
| |
| assert_eq!(agent_cwd_path, expected_agent_cwd_path); |
|
|
| |
| assert_ne!(agent_path, agent_cwd_path); |
| } |
|
|
| #[test] |
| fn test_global_skills_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let actual = fixture.global_skills_path(); |
| let expected = PathBuf::from("/home/user/.forge/skills"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_agents_skills_path_with_home() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.home(PathBuf::from("/home/user")); |
|
|
| let actual = fixture.agents_skills_path(); |
| let expected = Some(PathBuf::from("/home/user/.agents/skills")); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_agents_skills_path_without_home() { |
| let fixture: Environment = Faker.fake(); |
| |
| let mut fixture = fixture; |
| fixture.home = None; |
|
|
| let actual = fixture.agents_skills_path(); |
|
|
| assert_eq!(actual, None); |
| } |
|
|
| #[test] |
| fn test_local_skills_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.cwd(PathBuf::from("/projects/my-app")); |
|
|
| let actual = fixture.local_skills_path(); |
| let expected = PathBuf::from("/projects/my-app/.forge/skills"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_skills_paths_independent() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture |
| .cwd(PathBuf::from("/projects/my-app")) |
| .base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let global_path = fixture.global_skills_path(); |
| let local_path = fixture.local_skills_path(); |
|
|
| let expected_global = PathBuf::from("/home/user/.forge/skills"); |
| let expected_local = PathBuf::from("/projects/my-app/.forge/skills"); |
|
|
| |
| assert_eq!(global_path, expected_global); |
|
|
| |
| assert_eq!(local_path, expected_local); |
|
|
| |
| assert_ne!(global_path, local_path); |
| } |
|
|
| #[test] |
| fn test_command_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let actual = fixture.command_path(); |
| let expected = PathBuf::from("/home/user/.forge/commands"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_command_path_local() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.cwd(PathBuf::from("/projects/my-app")); |
|
|
| let actual = fixture.command_path_local(); |
| let expected = PathBuf::from("/projects/my-app/.forge/commands"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_command_paths_independent() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture |
| .cwd(PathBuf::from("/projects/my-app")) |
| .base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let global_path = fixture.command_path(); |
| let local_path = fixture.command_path_local(); |
|
|
| let expected_global = PathBuf::from("/home/user/.forge/commands"); |
| let expected_local = PathBuf::from("/projects/my-app/.forge/commands"); |
|
|
| assert_eq!(global_path, expected_global); |
| assert_eq!(local_path, expected_local); |
| assert_ne!(global_path, local_path); |
| } |
|
|
| #[test] |
| fn test_global_agents_md_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let actual = fixture.global_agentsmd_path(); |
| let expected = PathBuf::from("/home/user/.forge/AGENTS.md"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_local_agents_md_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.cwd(PathBuf::from("/projects/my-app")); |
|
|
| let actual = fixture.local_agentsmd_path(); |
| let expected = PathBuf::from("/projects/my-app/AGENTS.md"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_plans_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.cwd(PathBuf::from("/projects/my-app")); |
|
|
| let actual = fixture.plans_path(); |
| let expected = PathBuf::from("/projects/my-app/plans"); |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_provider_config_path() { |
| let fixture: Environment = Faker.fake(); |
| let fixture = fixture.base_path(PathBuf::from("/home/user/.forge")); |
|
|
| let actual = fixture.provider_config_path(); |
| let expected = PathBuf::from("/home/user/.forge/provider.json"); |
|
|
| assert_eq!(actual, expected); |
| } |
| } |
|
|