aboutsummaryrefslogtreecommitdiff
path: root/src/integration/env.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/integration/env.rs')
-rw-r--r--src/integration/env.rs156
1 files changed, 156 insertions, 0 deletions
diff --git a/src/integration/env.rs b/src/integration/env.rs
new file mode 100644
index 00000000..575c4c9a
--- /dev/null
+++ b/src/integration/env.rs
@@ -0,0 +1,156 @@
+use std::io;
+use std::path::PathBuf;
+#[cfg(test)]
+use std::sync::{Mutex, MutexGuard, OnceLock};
+
+use portable_pty::CommandBuilder;
+
+pub(crate) const HERDR_PANE_ID_ENV_VAR: &str = "HERDR_PANE_ID";
+pub(crate) const HERDR_TAB_ID_ENV_VAR: &str = "HERDR_TAB_ID";
+pub(crate) const HERDR_WORKSPACE_ID_ENV_VAR: &str = "HERDR_WORKSPACE_ID";
+
+pub(crate) const PI_CODING_AGENT_DIR_ENV_VAR: &str = "PI_CODING_AGENT_DIR";
+pub(crate) const CLAUDE_CONFIG_DIR_ENV_VAR: &str = "CLAUDE_CONFIG_DIR";
+pub(crate) const CODEX_HOME_ENV_VAR: &str = "CODEX_HOME";
+pub(crate) const KIMI_CODE_HOME_ENV_VAR: &str = "KIMI_CODE_HOME";
+pub(crate) const COPILOT_HOME_ENV_VAR: &str = "COPILOT_HOME";
+pub(crate) const QODERCLI_CONFIG_DIR_ENV_VAR: &str = "QODER_CONFIG_DIR";
+pub(crate) const CURSOR_CONFIG_DIR_ENV_VAR: &str = "CURSOR_CONFIG_DIR";
+
+pub(crate) fn apply_pane_base_env(cmd: &mut CommandBuilder) {
+ cmd.env(crate::api::SOCKET_PATH_ENV_VAR, crate::api::socket_path());
+}
+
+pub(crate) fn pi_extension_dir() -> io::Result<PathBuf> {
+ Ok(
+ config_dir_from_env_or_home(PI_CODING_AGENT_DIR_ENV_VAR, &[".pi", "agent"])?
+ .join("extensions"),
+ )
+}
+
+pub(crate) fn omp_extension_dir() -> io::Result<PathBuf> {
+ Ok(
+ config_dir_from_env_or_home(PI_CODING_AGENT_DIR_ENV_VAR, &[".omp", "agent"])?
+ .join("extensions"),
+ )
+}
+
+pub(crate) fn claude_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(CLAUDE_CONFIG_DIR_ENV_VAR, &[".claude"])
+}
+
+pub(crate) fn codex_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(CODEX_HOME_ENV_VAR, &[".codex"])
+}
+
+pub(crate) fn kimi_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(KIMI_CODE_HOME_ENV_VAR, &[".kimi-code"])
+}
+
+pub(crate) fn copilot_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(COPILOT_HOME_ENV_VAR, &[".copilot"])
+}
+
+pub(crate) fn devin_dir() -> io::Result<PathBuf> {
+ if let Some(value) = std::env::var_os("XDG_CONFIG_HOME").filter(|value| !value.is_empty()) {
+ return expand_tilde_path(PathBuf::from(value)).map(|path| path.join("devin"));
+ }
+
+ Ok(home_dir()?.join(".config").join("devin"))
+}
+
+pub(crate) fn droid_dir() -> io::Result<PathBuf> {
+ Ok(home_dir()?.join(".factory"))
+}
+
+pub(crate) 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)
+}
+
+pub(crate) 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)
+}
+
+pub(crate) fn opencode_dir() -> io::Result<PathBuf> {
+ Ok(home_dir()?.join(".config/opencode"))
+}
+
+pub(crate) fn kilo_dir() -> io::Result<PathBuf> {
+ Ok(home_dir()?.join(".config/kilo"))
+}
+
+pub(crate) fn hermes_dir() -> io::Result<PathBuf> {
+ Ok(home_dir()?.join(".hermes"))
+}
+
+pub(crate) fn hermes_plugin_dir() -> io::Result<PathBuf> {
+ Ok(hermes_dir()?
+ .join("plugins")
+ .join(super::HERMES_PLUGIN_INSTALL_NAME))
+}
+
+pub(crate) fn qodercli_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(QODERCLI_CONFIG_DIR_ENV_VAR, &[".qoder"])
+}
+
+pub(crate) fn cursor_dir() -> io::Result<PathBuf> {
+ config_dir_from_env_or_home(CURSOR_CONFIG_DIR_ENV_VAR, &[".cursor"])
+}
+
+pub(crate) fn home_dir() -> io::Result<PathBuf> {
+ if let Some(home) = std::env::var_os("HOME").filter(|value| !value.is_empty()) {
+ return Ok(PathBuf::from(home));
+ }
+
+ #[cfg(windows)]
+ {
+ if let Some(profile) = std::env::var_os("USERPROFILE").filter(|value| !value.is_empty()) {
+ return Ok(PathBuf::from(profile));
+ }
+ if let (Some(drive), Some(path)) = (
+ std::env::var_os("HOMEDRIVE").filter(|value| !value.is_empty()),
+ std::env::var_os("HOMEPATH").filter(|value| !value.is_empty()),
+ ) {
+ let mut home = PathBuf::from(drive);
+ home.push(path);
+ return Ok(home);
+ }
+ }
+
+ Err(io::Error::other(
+ "home directory is not set; cannot locate home directory",
+ ))
+}
+
+#[cfg(test)]
+pub(crate) fn integration_env_lock() -> MutexGuard<'static, ()> {
+ static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
+ LOCK.get_or_init(|| Mutex::new(())).lock().unwrap()
+}