diff options
| author | akbash <akbash@herdr.dev> | 2026-08-30 03:03:25 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-30 03:03:25 +0300 |
| commit | 4a3b04f59ba3b7d8a15cea187b23e1e80c343b0c (patch) | |
| tree | e1a38be588e702c39c434092f8b7a7a218e88f7c /src | |
| parent | 3a3792622e59c7f2dc20f9c0236167161e4a5035 (diff) | |
fix: encode antigravity windows hook command (#3352)
refs #3348
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/integration/assets/antigravity_cli/herdr-agent-state.ps1 | 2 | ||||
| -rw-r--r-- | src/integration/assets/antigravity_cli/herdr-agent-state.sh | 2 | ||||
| -rw-r--r-- | src/integration/command.rs | 15 | ||||
| -rw-r--r-- | src/integration/mod.rs | 2 | ||||
| -rw-r--r-- | src/integration/targets.rs | 26 | ||||
| -rw-r--r-- | src/integration/tests.rs | 73 |
6 files changed, 104 insertions, 16 deletions
diff --git a/src/integration/assets/antigravity_cli/herdr-agent-state.ps1 b/src/integration/assets/antigravity_cli/herdr-agent-state.ps1 index 04717757..e446a431 100644 --- a/src/integration/assets/antigravity_cli/herdr-agent-state.ps1 +++ b/src/integration/assets/antigravity_cli/herdr-agent-state.ps1 @@ -2,7 +2,7 @@ # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. # HERDR_INTEGRATION_ID=antigravity_cli -# HERDR_INTEGRATION_VERSION=2 +# HERDR_INTEGRATION_VERSION=3 # Session-only: this hook reports the Antigravity conversation so Herdr can # resume the pane. Lifecycle state comes from Herdr's screen detection. diff --git a/src/integration/assets/antigravity_cli/herdr-agent-state.sh b/src/integration/assets/antigravity_cli/herdr-agent-state.sh index 859ec378..2e55afad 100644 --- a/src/integration/assets/antigravity_cli/herdr-agent-state.sh +++ b/src/integration/assets/antigravity_cli/herdr-agent-state.sh @@ -3,7 +3,7 @@ # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. # HERDR_INTEGRATION_ID=antigravity_cli -# HERDR_INTEGRATION_VERSION=2 +# HERDR_INTEGRATION_VERSION=3 # Session-only: this hook reports the Antigravity conversation so Herdr can # resume the pane. Lifecycle state comes from Herdr's screen detection. diff --git a/src/integration/command.rs b/src/integration/command.rs index a5d58f0c..144fab4a 100644 --- a/src/integration/command.rs +++ b/src/integration/command.rs @@ -1,5 +1,8 @@ use std::path::Path; +#[cfg(any(windows, test))] +use base64::Engine; + pub(crate) fn shell_single_quote(value: &str) -> String { format!("'{}'", value.replace('\'', "'\"'\"'")) } @@ -30,6 +33,18 @@ pub(crate) fn hook_command(hook_path: &Path, action: Option<&str>) -> String { } } +#[cfg(any(windows, test))] +pub(crate) fn powershell_encoded_hook_command(hook_path: &Path, action: &str) -> String { + let path = hook_path.display().to_string().replace('\'', "''"); + let script = format!("& '{path}' {action}"); + let encoded_script = script + .encode_utf16() + .flat_map(u16::to_le_bytes) + .collect::<Vec<_>>(); + let encoded = base64::engine::general_purpose::STANDARD.encode(encoded_script); + format!("powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}") +} + pub(crate) fn legacy_bash_hook_command(hook_path: &Path, action: Option<&str>) -> String { let mut command = format!( "bash {}", diff --git a/src/integration/mod.rs b/src/integration/mod.rs index 4085b447..fd0839f4 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -239,7 +239,7 @@ const ANTIGRAVITY_CLI_HOOK_ASSET: &str = #[cfg(not(windows))] const ANTIGRAVITY_CLI_HOOK_ASSET: &str = include_str!("assets/antigravity_cli/herdr-agent-state.sh"); -const ANTIGRAVITY_CLI_INTEGRATION_VERSION: u32 = 2; +const ANTIGRAVITY_CLI_INTEGRATION_VERSION: u32 = 3; /// Antigravity CLI keys `hooks.json` by hook name, so every Herdr entry lives /// under one Herdr-owned block that install rewrites and uninstall removes. const ANTIGRAVITY_CLI_HOOK_BLOCK_NAME: &str = "herdr"; diff --git a/src/integration/targets.rs b/src/integration/targets.rs index 0150ccaa..0690fc90 100644 --- a/src/integration/targets.rs +++ b/src/integration/targets.rs @@ -8,6 +8,8 @@ use super::claude_settings::{ install as install_claude_settings, uninstall as uninstall_claude_settings, }; use super::command::hook_command; +#[cfg(windows)] +use super::command::powershell_encoded_hook_command; #[cfg(not(windows))] use super::command::shell_single_quote; use super::config_edit::{ @@ -1190,16 +1192,7 @@ pub(crate) fn uninstall_cursor() -> io::Result<CursorUninstallResult> { pub(crate) fn mastracode_hook_command(hook_path: &Path, action: &str) -> String { #[cfg(windows)] { - use base64::Engine; - - let path = hook_path.display().to_string().replace('\'', "''"); - let script = format!("& '{path}' {action}"); - let encoded_script = script - .encode_utf16() - .flat_map(u16::to_le_bytes) - .collect::<Vec<_>>(); - let encoded = base64::engine::general_purpose::STANDARD.encode(encoded_script); - format!("powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}") + powershell_encoded_hook_command(hook_path, action) } #[cfg(not(windows))] { @@ -1349,6 +1342,17 @@ pub(crate) fn install_antigravity_cli() -> io::Result<AntigravityCliInstallPaths }) } +pub(crate) fn antigravity_cli_hook_command(hook_path: &Path, action: &str) -> String { + #[cfg(windows)] + { + powershell_encoded_hook_command(hook_path, action) + } + #[cfg(not(windows))] + { + hook_command(hook_path, Some(action)) + } +} + /// Builds the Herdr-owned `hooks.json` block for Antigravity CLI. /// /// Every event Herdr registers takes a flat handler list; the `matcher`/`hooks` @@ -1358,7 +1362,7 @@ fn antigravity_cli_hook_block(hook_path: &Path) -> Value { for (event, action) in ANTIGRAVITY_CLI_HOOK_EVENTS { let handler = json!({ "type": "command", - "command": hook_command(hook_path, Some(action)), + "command": antigravity_cli_hook_command(hook_path, action), "timeout": ANTIGRAVITY_CLI_HOOK_TIMEOUT_SEC, }); block.insert(event.to_string(), json!([handler])); diff --git a/src/integration/tests.rs b/src/integration/tests.rs index 7cac9145..c2d94a6e 100644 --- a/src/integration/tests.rs +++ b/src/integration/tests.rs @@ -14,6 +14,40 @@ use std::path::{Path, PathBuf}; use serde_json::{json, Map, Value}; #[test] +fn windows_powershell_encoded_hook_command_preserves_script_invocation() { + use base64::Engine; + + let hook_path = Path::new(r"C:\Users\O'Neil λ\App Data\hooks\herdr-agent-state.ps1"); + let command = powershell_encoded_hook_command(hook_path, "session"); + let encoded = command + .strip_prefix("powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ") + .expect("encoded PowerShell command"); + let bytes = base64::engine::general_purpose::STANDARD + .decode(encoded) + .expect("base64 payload"); + let mut chunks = bytes.chunks_exact(2); + let utf16 = chunks + .by_ref() + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect::<Vec<_>>(); + assert!(chunks.remainder().is_empty(), "UTF-16LE payload"); + assert_eq!( + String::from_utf16(&utf16).expect("PowerShell script"), + r"& 'C:\Users\O''Neil λ\App Data\hooks\herdr-agent-state.ps1' session" + ); +} + +#[cfg(windows)] +#[test] +fn windows_antigravity_cli_hook_command_uses_encoded_powershell() { + let hook_path = Path::new(r"C:\Users\reporter\.gemini\config\hooks\herdr-agent-state.ps1"); + assert_eq!( + antigravity_cli_hook_command(hook_path, "session"), + powershell_encoded_hook_command(hook_path, "session") + ); +} + +#[test] fn extract_version_triple_parses_common_outputs() { assert_eq!(extract_version_triple("0.14.0"), Some((0, 14, 0))); assert_eq!(extract_version_triple("v1.2.3"), Some((1, 2, 3))); @@ -3889,8 +3923,10 @@ fn install_antigravity_cli_writes_hook_and_updates_hooks_json() { Some(ANTIGRAVITY_CLI_HOOK_TIMEOUT_SEC) ); let command = handler.get("command").and_then(Value::as_str).unwrap(); - assert!(command.contains("herdr-agent-state")); - assert!(command.ends_with(action)); + assert_eq!( + command, + antigravity_cli_hook_command(&installed.hook_path, action) + ); } // The integration is session-only. Antigravity CLI cannot express blocked @@ -3921,6 +3957,39 @@ fn install_antigravity_cli_writes_hook_and_updates_hooks_json() { } #[test] +fn antigravity_cli_v2_install_is_outdated_until_reinstalled() { + let _lock = integration_env_lock(); + let base = unique_base(); + let agy_dir = base.join(".gemini").join("config"); + let hook_dir = agy_dir.join("hooks"); + fs::create_dir_all(&hook_dir).unwrap(); + fs::write( + hook_dir.join(ANTIGRAVITY_CLI_HOOK_INSTALL_NAME), + ANTIGRAVITY_CLI_HOOK_ASSET + .replace("HERDR_INTEGRATION_VERSION=3", "HERDR_INTEGRATION_VERSION=2"), + ) + .unwrap(); + std::env::set_var(ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR, &agy_dir); + + let status = || { + installed_integration_statuses() + .into_iter() + .find(|status| status.target == crate::api::schema::IntegrationTarget::AntigravityCli) + .expect("antigravity cli integration status") + }; + let outdated = status(); + assert_eq!(outdated.state, IntegrationStatusKind::Outdated); + assert_eq!(outdated.installed_version, Some(2)); + assert_eq!(outdated.expected_version, 3); + + install_antigravity_cli().unwrap(); + assert_eq!(status().state, IntegrationStatusKind::Current); + + std::env::remove_var(ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR); + let _ = fs::remove_dir_all(base); +} + +#[test] fn install_antigravity_cli_rewrites_stale_herdr_block() { let _lock = integration_env_lock(); let base = unique_base(); |
