//! Agent integrations: hook extensions that let agents report their own //! state (herdr's integration model), so detection does not rely on screen //! scraping for the agents that support it. use std::path::PathBuf; use detect_core::Agent; /// The embedded pi extension, adapted from herdr's `herdr-agent-state.ts` /// (Apache-2.0, Copyright herdr contributors). const PI_EXTENSION: &str = include_str!("../assets/pi/pi-agent-state.ts"); fn pi_extension_dir() -> Result { let base = std::env::var_os("PI_CODING_AGENT_DIR") .map(PathBuf::from) .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".pi").join("agent"))) .ok_or("cannot determine pi agent dir (no HOME)")?; Ok(base.join("extensions")) } /// zsh hook: records the command line running in each tmux pane exactly as /// typed (aliases, pipes, quoting), for restore to retype. const ZSH_HOOK: &str = r#"# installed by dingger; `dingger install zsh` overwrites this file. # # Records the command line running in this tmux pane, as typed, in the pane # option @dingger_cmd, so `dingger restore` can type it again (without # running it) after a crash or shutdown. if [[ -n $TMUX_PANE ]]; then _dingger_preexec() { command tmux set-option -p -t "$TMUX_PANE" @dingger_cmd "$1" 2>/dev/null &! } _dingger_precmd() { command tmux set-option -p -u -t "$TMUX_PANE" @dingger_cmd 2>/dev/null &! } autoload -Uz add-zsh-hook add-zsh-hook preexec _dingger_preexec add-zsh-hook precmd _dingger_precmd fi "#; fn config_dir() -> Result { std::env::var_os("DINGGER_CONFIG_DIR") .map(PathBuf::from) .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".config").join("dingger"))) .ok_or_else(|| "cannot determine the config dir (no HOME)".to_string()) } fn install_zsh() -> Result<(), String> { let dir = config_dir()?; std::fs::create_dir_all(&dir).map_err(|err| format!("mkdir failed: {err}"))?; let path = dir.join("dingger.zsh"); std::fs::write(&path, ZSH_HOOK).map_err(|err| format!("write failed: {err}"))?; println!( "installed {}\nadd this line to your .zshrc (dingger does not edit it):\n\n [[ -r {0} ]] && source {0}\n", path.display() ); Ok(()) } /// Install an integration: an agent's state hooks, or `zsh` for retyping /// command lines on restore. pub fn install(agent: &str) -> Result<(), String> { if agent == "zsh" { return install_zsh(); } let parsed = detect_core::parse_agent_label(agent) .ok_or_else(|| format!("unknown agent: {agent}"))?; match parsed { Agent::Pi => install_pi(), other => Err(format!( "no hook integration for {} yet — screen detection is used", detect_core::agent_label(other) )), } } fn install_pi() -> Result<(), String> { let dir = pi_extension_dir()?; std::fs::create_dir_all(&dir).map_err(|err| format!("mkdir failed: {err}"))?; let path = dir.join("dingger-agent-state.ts"); std::fs::write(&path, PI_EXTENSION).map_err(|err| format!("write failed: {err}"))?; tracing::info!(path = %path.display(), "installed pi extension"); println!( "installed {} — new pi sessions report their state directly;\nrestart running pi agents (or reload them) to pick it up", path.display() ); Ok(()) }