aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-08-30 03:03:25 +0300
committerGitHub <noreply@github.com>2026-08-30 03:03:25 +0300
commit4a3b04f59ba3b7d8a15cea187b23e1e80c343b0c (patch)
treee1a38be588e702c39c434092f8b7a7a218e88f7c /src
parent3a3792622e59c7f2dc20f9c0236167161e4a5035 (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.ps12
-rw-r--r--src/integration/assets/antigravity_cli/herdr-agent-state.sh2
-rw-r--r--src/integration/command.rs15
-rw-r--r--src/integration/mod.rs2
-rw-r--r--src/integration/targets.rs26
-rw-r--r--src/integration/tests.rs73
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();