parent
72770305d3
commit
170d567487
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Fixed
|
||||
- GitHub Copilot is now correctly detected when its process name is `copilot`.
|
||||
- Integration installs now respect `PI_CODING_AGENT_DIR`, `CLAUDE_CONFIG_DIR`, and `CODEX_HOME` when choosing Pi, Claude Code, and Codex config paths.
|
||||
- Split pane resize hit areas no longer overlap the first content column or row, making text selection work from the start of right and bottom panes.
|
||||
|
||||
## [0.5.9] - 2026-05-15
|
||||
|
|
|
|||
|
|
@ -55,6 +55,14 @@ this writes the bundled pi extension to:
|
|||
~/.pi/agent/extensions/herdr-agent-state.ts
|
||||
```
|
||||
|
||||
if `PI_CODING_AGENT_DIR` is set, herdr uses that agent directory instead and writes to:
|
||||
|
||||
```text
|
||||
$PI_CODING_AGENT_DIR/extensions/herdr-agent-state.ts
|
||||
```
|
||||
|
||||
`~` is expanded in `PI_CODING_AGENT_DIR`.
|
||||
|
||||
pi is the cleanest integration. it already has an authoritative hook model, so herdr can get direct state reports over the socket api without guessing as much from the terminal.
|
||||
|
||||
bundled source: [`src/integration/assets/pi/herdr-agent-state.ts`](./src/integration/assets/pi/herdr-agent-state.ts)
|
||||
|
|
@ -65,11 +73,7 @@ uninstall:
|
|||
herdr integration uninstall pi
|
||||
```
|
||||
|
||||
this removes:
|
||||
|
||||
```text
|
||||
~/.pi/agent/extensions/herdr-agent-state.ts
|
||||
```
|
||||
this removes the same extension path herdr would install, using `PI_CODING_AGENT_DIR` when it is set.
|
||||
|
||||
## claude code
|
||||
|
||||
|
|
@ -84,6 +88,8 @@ this:
|
|||
- writes the hook script to `~/.claude/hooks/herdr-agent-state.sh`
|
||||
- updates `~/.claude/settings.json`
|
||||
|
||||
if `CLAUDE_CONFIG_DIR` is set, herdr uses that directory instead, for example `$CLAUDE_CONFIG_DIR/hooks/herdr-agent-state.sh` and `$CLAUDE_CONFIG_DIR/settings.json`. `~` is expanded in `CLAUDE_CONFIG_DIR`.
|
||||
|
||||
bundled source: [`src/integration/assets/claude/herdr-agent-state.sh`](./src/integration/assets/claude/herdr-agent-state.sh)
|
||||
|
||||
current hook mapping:
|
||||
|
|
@ -109,10 +115,7 @@ uninstall:
|
|||
herdr integration uninstall claude
|
||||
```
|
||||
|
||||
this:
|
||||
|
||||
- removes `~/.claude/hooks/herdr-agent-state.sh`
|
||||
- removes herdr-owned hook entries from `~/.claude/settings.json`
|
||||
this removes the same hook path and settings entries herdr would install, using `CLAUDE_CONFIG_DIR` when it is set.
|
||||
|
||||
## codex
|
||||
|
||||
|
|
@ -129,6 +132,8 @@ this:
|
|||
- ensures `hooks = true` under `[features]` in `~/.codex/config.toml`
|
||||
- migrates the deprecated top-level `[features] codex_hooks = true` setting to `hooks = true`
|
||||
|
||||
if `CODEX_HOME` is set, herdr uses that directory instead, for example `$CODEX_HOME/herdr-agent-state.sh`, `$CODEX_HOME/hooks.json`, and `$CODEX_HOME/config.toml`. `~` is expanded in `CODEX_HOME`.
|
||||
|
||||
bundled source: [`src/integration/assets/codex/herdr-agent-state.sh`](./src/integration/assets/codex/herdr-agent-state.sh)
|
||||
|
||||
current hook mapping:
|
||||
|
|
@ -153,9 +158,9 @@ herdr integration uninstall codex
|
|||
|
||||
this:
|
||||
|
||||
- removes `~/.codex/herdr-agent-state.sh`
|
||||
- removes herdr-owned hook entries from `~/.codex/hooks.json`
|
||||
- intentionally leaves `~/.codex/config.toml` alone
|
||||
- removes the same hook path herdr would install, using `CODEX_HOME` when it is set
|
||||
- removes herdr-owned hook entries from the matching `hooks.json`
|
||||
- intentionally leaves the matching `config.toml` alone
|
||||
|
||||
that last point is deliberate: herdr does **not** try to guess whether `hooks = true` is still needed for some other codex hook setup.
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
@ -1058,6 +1106,42 @@ mod tests {
|
|||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
@ -1208,6 +1292,26 @@ mod tests {
|
|||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
@ -1377,6 +1481,25 @@ mod tests {
|
|||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
|
|||
|
|
@ -575,10 +575,13 @@ mod tests {
|
|||
let root_pane = ws.tabs[0].root_pane;
|
||||
ws.tabs[0]
|
||||
.pane_cwds
|
||||
.insert(root_pane, PathBuf::from("/tmp/pion"));
|
||||
.insert(root_pane, PathBuf::from("/herdr-test/pion"));
|
||||
|
||||
assert_eq!(ws.display_name(), "pion");
|
||||
assert_eq!(ws.resolved_identity_cwd(), Some(PathBuf::from("/tmp/pion")));
|
||||
assert_eq!(
|
||||
ws.resolved_identity_cwd(),
|
||||
Some(PathBuf::from("/herdr-test/pion"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue