diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-16 17:29:37 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-16 18:31:35 +0300 |
| commit | 170d5674872fe030777ceb97e18b38a0e9ddbc2f (patch) | |
| tree | fd7d19494902c5b3e93fee7e34b2e30b0569b23a /src/integration | |
| parent | 72770305d388fd5ec247493e05a89b686c6d0026 (diff) | |
fix: respect integration config dirs
refs #121
Diffstat (limited to 'src/integration')
| -rw-r--r-- | src/integration/mod.rs | 129 |
1 files changed, 126 insertions, 3 deletions
diff --git a/src/integration/mod.rs b/src/integration/mod.rs index fe92f66c..88f673ed 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -13,12 +13,15 @@ pub(crate) const HERDR_PANE_ID_ENV_VAR: &str = "HERDR_PANE_ID"; const PI_EXTENSION_INSTALL_NAME: &str = "herdr-agent-state.ts"; const PI_EXTENSION_ASSET: &str = include_str!("assets/pi/herdr-agent-state.ts"); const PI_INTEGRATION_VERSION: u32 = 1; +const PI_CODING_AGENT_DIR_ENV_VAR: &str = "PI_CODING_AGENT_DIR"; const CLAUDE_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh"; const CLAUDE_HOOK_ASSET: &str = include_str!("assets/claude/herdr-agent-state.sh"); const CLAUDE_INTEGRATION_VERSION: u32 = 1; +const CLAUDE_CONFIG_DIR_ENV_VAR: &str = "CLAUDE_CONFIG_DIR"; const CODEX_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh"; const CODEX_HOOK_ASSET: &str = include_str!("assets/codex/herdr-agent-state.sh"); const CODEX_INTEGRATION_VERSION: u32 = 2; +const CODEX_HOME_ENV_VAR: &str = "CODEX_HOME"; const OPENCODE_PLUGIN_INSTALL_NAME: &str = "herdr-agent-state.js"; const OPENCODE_PLUGIN_ASSET: &str = include_str!("assets/opencode/herdr-agent-state.js"); const OPENCODE_INTEGRATION_VERSION: u32 = 1; @@ -997,15 +1000,53 @@ fn make_executable(path: &Path) -> io::Result<()> { } fn pi_extension_dir() -> io::Result<PathBuf> { - Ok(home_dir()?.join(".pi/agent/extensions")) + Ok( + config_dir_from_env_or_home(PI_CODING_AGENT_DIR_ENV_VAR, &[".pi", "agent"])? + .join("extensions"), + ) } fn claude_dir() -> io::Result<PathBuf> { - Ok(home_dir()?.join(".claude")) + config_dir_from_env_or_home(CLAUDE_CONFIG_DIR_ENV_VAR, &[".claude"]) } fn codex_dir() -> io::Result<PathBuf> { - Ok(home_dir()?.join(".codex")) + config_dir_from_env_or_home(CODEX_HOME_ENV_VAR, &[".codex"]) +} + +fn config_dir_from_env_or_home( + env_var: &str, + home_relative_segments: &[&str], +) -> io::Result<PathBuf> { + if let Some(value) = std::env::var_os(env_var).filter(|value| !value.is_empty()) { + return expand_tilde_path(PathBuf::from(value)); + } + + let mut path = home_dir()?; + for segment in home_relative_segments { + path.push(segment); + } + Ok(path) +} + +fn expand_tilde_path(path: PathBuf) -> io::Result<PathBuf> { + let Some(raw) = path.to_str() else { + return Ok(path); + }; + + if raw == "~" { + return home_dir(); + } + + if let Some(rest) = raw + .strip_prefix("~/") + .or_else(|| raw.strip_prefix("~\\")) + .or_else(|| raw.strip_prefix('~')) + { + return Ok(home_dir()?.join(rest)); + } + + Ok(path) } fn opencode_dir() -> io::Result<PathBuf> { @@ -1028,7 +1069,14 @@ pub(crate) fn integration_env_lock() -> MutexGuard<'static, ()> { mod tests { use super::*; + fn clear_integration_path_env() { + std::env::remove_var(PI_CODING_AGENT_DIR_ENV_VAR); + std::env::remove_var(CLAUDE_CONFIG_DIR_ENV_VAR); + std::env::remove_var(CODEX_HOME_ENV_VAR); + } + fn unique_base() -> PathBuf { + clear_integration_path_env(); std::env::temp_dir().join(format!( "herdr-integration-install-test-{}-{}", std::process::id(), @@ -1059,6 +1107,42 @@ mod tests { } #[test] + fn install_pi_uses_pi_coding_agent_dir_env() { + let _lock = integration_env_lock(); + let base = unique_base(); + let agent_dir = base.join("custom-pi-agent"); + let ext_dir = agent_dir.join("extensions"); + fs::create_dir_all(&ext_dir).unwrap(); + std::env::set_var(PI_CODING_AGENT_DIR_ENV_VAR, &agent_dir); + + let path = install_pi().unwrap(); + + assert_eq!(path, ext_dir.join(PI_EXTENSION_INSTALL_NAME)); + + clear_integration_path_env(); + let _ = fs::remove_dir_all(base); + } + + #[test] + fn install_pi_expands_tilde_in_pi_coding_agent_dir_env() { + let _lock = integration_env_lock(); + let base = unique_base(); + let home = base.join("home"); + let ext_dir = home.join("custom-pi-agent/extensions"); + fs::create_dir_all(&ext_dir).unwrap(); + std::env::set_var("HOME", &home); + std::env::set_var(PI_CODING_AGENT_DIR_ENV_VAR, "~/custom-pi-agent"); + + let path = install_pi().unwrap(); + + assert_eq!(path, ext_dir.join(PI_EXTENSION_INSTALL_NAME)); + + std::env::remove_var("HOME"); + clear_integration_path_env(); + let _ = fs::remove_dir_all(base); + } + + #[test] fn uninstall_pi_removes_embedded_extension_when_present() { let _lock = integration_env_lock(); let base = unique_base(); @@ -1209,6 +1293,26 @@ mod tests { } #[test] + fn install_claude_uses_claude_config_dir_env() { + let _lock = integration_env_lock(); + let base = unique_base(); + let claude_dir = base.join("custom-claude"); + fs::create_dir_all(&claude_dir).unwrap(); + std::env::set_var(CLAUDE_CONFIG_DIR_ENV_VAR, &claude_dir); + + let installed = install_claude().unwrap(); + + assert_eq!(installed.settings_path, claude_dir.join("settings.json")); + assert_eq!( + installed.hook_path, + claude_dir.join("hooks").join(CLAUDE_HOOK_INSTALL_NAME) + ); + + clear_integration_path_env(); + let _ = fs::remove_dir_all(base); + } + + #[test] fn install_claude_is_idempotent_for_hook_entries() { let _lock = integration_env_lock(); let base = unique_base(); @@ -1378,6 +1482,25 @@ mod tests { } #[test] + fn install_codex_uses_codex_home_env() { + let _lock = integration_env_lock(); + let base = unique_base(); + let codex_dir = base.join("custom-codex"); + fs::create_dir_all(&codex_dir).unwrap(); + fs::write(codex_dir.join("config.toml"), "model = \"gpt-5.4\"\n").unwrap(); + std::env::set_var(CODEX_HOME_ENV_VAR, &codex_dir); + + let installed = install_codex().unwrap(); + + assert_eq!(installed.hook_path, codex_dir.join(CODEX_HOOK_INSTALL_NAME)); + assert_eq!(installed.hooks_path, codex_dir.join("hooks.json")); + assert_eq!(installed.config_path, codex_dir.join("config.toml")); + + clear_integration_path_env(); + let _ = fs::remove_dir_all(base); + } + + #[test] fn install_codex_is_idempotent_for_hook_entries_and_feature_flag() { let _lock = integration_env_lock(); let base = unique_base(); |
