diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-13 03:00:35 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-13 03:00:35 +0300 |
| commit | a4d52ab60282b2ad021cb90978a7ed4d3898704b (patch) | |
| tree | cdd5f55d6d392d54541b86da7f1a00a48c211d03 /src/integration | |
| parent | 8b635c918066dd184bacc29b4c06c40a3efea3f5 (diff) | |
feat: add qwen code detection and restore (#2743)
refs #2730
Diffstat (limited to 'src/integration')
| -rw-r--r-- | src/integration/actions.rs | 44 | ||||
| -rw-r--r-- | src/integration/assets/qwen/herdr-agent-session.ps1 | 37 | ||||
| -rw-r--r-- | src/integration/assets/qwen/herdr-agent-session.sh | 48 | ||||
| -rw-r--r-- | src/integration/env.rs | 5 | ||||
| -rw-r--r-- | src/integration/mod.rs | 12 | ||||
| -rw-r--r-- | src/integration/registry.rs | 10 | ||||
| -rw-r--r-- | src/integration/targets.rs | 97 | ||||
| -rw-r--r-- | src/integration/tests.rs | 104 | ||||
| -rw-r--r-- | src/integration/types.rs | 14 |
9 files changed, 367 insertions, 4 deletions
diff --git a/src/integration/actions.rs b/src/integration/actions.rs index 00bf8c8f..24d1f9e3 100644 --- a/src/integration/actions.rs +++ b/src/integration/actions.rs @@ -4,11 +4,11 @@ use super::registry::{integration_target_label, integration_target_supported}; use super::targets::{ install_antigravity_cli, install_claude, install_codex, install_copilot, install_cursor, install_devin, install_droid, install_grok, install_hermes, install_kilo, install_kimi, - install_mastracode, install_omp, install_opencode, install_pi, install_qodercli, + install_mastracode, install_omp, install_opencode, install_pi, install_qodercli, install_qwen, uninstall_antigravity_cli, uninstall_claude, uninstall_codex, uninstall_copilot, uninstall_cursor, uninstall_devin, uninstall_droid, uninstall_grok, uninstall_hermes, uninstall_kilo, uninstall_kimi, uninstall_mastracode, uninstall_omp, uninstall_opencode, - uninstall_pi, uninstall_qodercli, + uninstall_pi, uninstall_qodercli, uninstall_qwen, }; use super::version::{agent_version_requirement, enforce_agent_version}; use super::{KIMI_MIN_VERSION, PI_EXTENSION_INSTALL_NAME}; @@ -192,6 +192,19 @@ fn install_target_inner(target: crate::api::schema::IntegrationTarget) -> io::Re ), ] } + crate::api::schema::IntegrationTarget::Qwen => { + let installed = install_qwen()?; + vec![ + format!( + "installed qwen integration hook to {}", + installed.hook_path.display() + ), + format!( + "ensured qwen settings at {}", + installed.settings_path.display() + ), + ] + } crate::api::schema::IntegrationTarget::Cursor => { let installed = install_cursor()?; vec![ @@ -559,6 +572,33 @@ pub(crate) fn uninstall_target( } messages } + crate::api::schema::IntegrationTarget::Qwen => { + let result = uninstall_qwen()?; + let mut messages = Vec::new(); + if result.removed_hook_file { + messages.push(format!( + "removed qwen hook at {}", + result.hook_path.display() + )); + } else { + messages.push(format!( + "no qwen hook found at {}", + result.hook_path.display() + )); + } + if result.updated_settings { + messages.push(format!( + "removed herdr qwen hook entries from {}", + result.settings_path.display() + )); + } else { + messages.push(format!( + "no herdr qwen hook entries found in {}", + result.settings_path.display() + )); + } + messages + } crate::api::schema::IntegrationTarget::Cursor => { let result = uninstall_cursor()?; let mut messages = Vec::new(); diff --git a/src/integration/assets/qwen/herdr-agent-session.ps1 b/src/integration/assets/qwen/herdr-agent-session.ps1 new file mode 100644 index 00000000..882caf86 --- /dev/null +++ b/src/integration/assets/qwen/herdr-agent-session.ps1 @@ -0,0 +1,37 @@ +# installed by herdr +# managed by herdr; reinstalling or updating the integration overwrites this file. +# add custom hooks beside this file instead of editing it. +# HERDR_INTEGRATION_ID=qwen +# HERDR_INTEGRATION_VERSION=1 + +param([string]$Action = "") + +if ($Action -ne "session") { exit 0 } +if ($env:HERDR_ENV -ne "1") { exit 0 } +if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 } +if ([string]::IsNullOrWhiteSpace($env:HERDR_SOCKET_PATH)) { exit 0 } + +$inputText = [Console]::In.ReadToEnd() +try { + $payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json } +} catch { + $payload = $null +} + +if ($null -eq $payload -or [string]::IsNullOrWhiteSpace($payload.session_id)) { exit 0 } + +$seq = [DateTime]::UtcNow.Ticks +$herdr = if ([string]::IsNullOrWhiteSpace($env:HERDR_BIN_PATH)) { "herdr" } else { $env:HERDR_BIN_PATH } +$commandArgs = @( + "pane", "report-agent-session", $env:HERDR_PANE_ID, + "--source", "herdr:qwen", "--agent", "qwen", + "--agent-session-id", [string]$payload.session_id, + "--seq", [string]$seq +) +if ($payload.source -in @("startup", "resume", "clear", "compact", "branch")) { + $commandArgs += @("--session-start-source", [string]$payload.source) +} +try { + & $herdr @commandArgs 2>$null | Out-Null +} catch { +} diff --git a/src/integration/assets/qwen/herdr-agent-session.sh b/src/integration/assets/qwen/herdr-agent-session.sh new file mode 100644 index 00000000..226c2a97 --- /dev/null +++ b/src/integration/assets/qwen/herdr-agent-session.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# managed by herdr; reinstalling the integration replaces this file. +# HERDR_INTEGRATION_ID=qwen +# HERDR_INTEGRATION_VERSION=1 + +[ "${1:-}" = "session" ] || exit 0 +[ "${HERDR_ENV:-}" = "1" ] || exit 0 +[ -n "${HERDR_PANE_ID:-}" ] || exit 0 +[ -n "${HERDR_SOCKET_PATH:-}" ] || exit 0 +if [ -n "${HERDR_BIN_PATH:-}" ]; then + [ -x "$HERDR_BIN_PATH" ] || exit 0 +else + command -v herdr >/dev/null 2>&1 || exit 0 +fi +command -v python3 >/dev/null 2>&1 || exit 0 + +python3 -c ' +import json +import os +import subprocess +import sys +import time + +try: + payload = json.load(sys.stdin) + session_id = payload.get("session_id") + source = payload.get("source") + if not isinstance(session_id, str) or not session_id: + raise ValueError + command = os.environ.get("HERDR_BIN_PATH") or "herdr" + args = [ + command, "pane", "report-agent-session", os.environ["HERDR_PANE_ID"], + "--source", "herdr:qwen", "--agent", "qwen", + "--agent-session-id", session_id, "--seq", str(time.time_ns()), + ] + if source in ("startup", "resume", "clear", "compact", "branch"): + args.extend(["--session-start-source", source]) + subprocess.run( + args, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=1, + check=False, + ) +except Exception: + pass +' 2>/dev/null || true diff --git a/src/integration/env.rs b/src/integration/env.rs index e86f6ee8..8f3a9e65 100644 --- a/src/integration/env.rs +++ b/src/integration/env.rs @@ -16,6 +16,7 @@ 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 QWEN_HOME_ENV_VAR: &str = "QWEN_HOME"; pub(crate) const CURSOR_CONFIG_DIR_ENV_VAR: &str = "CURSOR_CONFIG_DIR"; pub(crate) const ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR: &str = "ANTIGRAVITY_CLI_CONFIG_DIR"; pub(crate) const GROK_CONFIG_DIR_ENV_VAR: &str = "GROK_CONFIG_DIR"; @@ -157,6 +158,10 @@ pub(crate) fn qodercli_dir() -> io::Result<PathBuf> { config_dir_from_env_or_home(QODERCLI_CONFIG_DIR_ENV_VAR, &[".qoder"]) } +pub(crate) fn qwen_dir() -> io::Result<PathBuf> { + config_dir_from_env_or_home(QWEN_HOME_ENV_VAR, &[".qwen"]) +} + pub(crate) fn cursor_dir() -> io::Result<PathBuf> { config_dir_from_env_or_home(CURSOR_CONFIG_DIR_ENV_VAR, &[".cursor"]) } diff --git a/src/integration/mod.rs b/src/integration/mod.rs index 597aabc5..c87ccac6 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -192,6 +192,18 @@ const QODERCLI_HOOK_ASSET: &str = if cfg!(windows) { }; const QODERCLI_INTEGRATION_VERSION: u32 = 3; const QODERCLI_HOOK_EVENTS: [(&str, &str); 1] = [("SessionStart", "session")]; +const QWEN_HOOK_INSTALL_NAME: &str = if cfg!(windows) { + "herdr-agent-session.ps1" +} else { + "herdr-agent-session.sh" +}; +const QWEN_HOOK_ASSET: &str = if cfg!(windows) { + include_str!("assets/qwen/herdr-agent-session.ps1") +} else { + include_str!("assets/qwen/herdr-agent-session.sh") +}; +const QWEN_INTEGRATION_VERSION: u32 = 1; +const QWEN_HOOK_EVENTS: [(&str, &str); 1] = [("SessionStart", "session")]; const QODERCLI_REMOVED_LIFECYCLE_HOOK_EVENTS: [(&str, &str); 12] = [ ("SessionStart", "idle"), ("UserPromptSubmit", "working"), diff --git a/src/integration/registry.rs b/src/integration/registry.rs index b7bb3bf2..46251970 100644 --- a/src/integration/registry.rs +++ b/src/integration/registry.rs @@ -20,6 +20,7 @@ pub(crate) fn integration_target_label( crate::api::schema::IntegrationTarget::Kilo => "kilo", crate::api::schema::IntegrationTarget::Hermes => "hermes", crate::api::schema::IntegrationTarget::Qodercli => "qodercli", + crate::api::schema::IntegrationTarget::Qwen => "qwen", crate::api::schema::IntegrationTarget::Cursor => "cursor", crate::api::schema::IntegrationTarget::Mastracode => "mastracode", crate::api::schema::IntegrationTarget::AntigravityCli => "antigravity-cli", @@ -49,6 +50,7 @@ pub(crate) fn integration_target_command_names( crate::api::schema::IntegrationTarget::Kilo => &["kilo", "kilo-code"], crate::api::schema::IntegrationTarget::Hermes => &["hermes"], crate::api::schema::IntegrationTarget::Qodercli => qodercli_command_names(), + crate::api::schema::IntegrationTarget::Qwen => &["qwen"], crate::api::schema::IntegrationTarget::Cursor => cursor_command_names(), crate::api::schema::IntegrationTarget::Mastracode => &["mastracode"], crate::api::schema::IntegrationTarget::AntigravityCli => &["agy"], @@ -75,6 +77,7 @@ pub(crate) fn integration_target_supported(target: crate::api::schema::Integrati | crate::api::schema::IntegrationTarget::Droid | crate::api::schema::IntegrationTarget::Kimi | crate::api::schema::IntegrationTarget::Qodercli + | crate::api::schema::IntegrationTarget::Qwen | crate::api::schema::IntegrationTarget::AntigravityCli | crate::api::schema::IntegrationTarget::Devin | crate::api::schema::IntegrationTarget::Hermes @@ -264,7 +267,7 @@ fn integration_specs() -> [( crate::api::schema::IntegrationTarget, io::Result<PathBuf>, u32, -); 16] { +); 17] { [ ( crate::api::schema::IntegrationTarget::Pi, @@ -330,6 +333,11 @@ fn integration_specs() -> [( super::QODERCLI_INTEGRATION_VERSION, ), ( + crate::api::schema::IntegrationTarget::Qwen, + qwen_dir().map(|dir| dir.join("hooks").join(super::QWEN_HOOK_INSTALL_NAME)), + super::QWEN_INTEGRATION_VERSION, + ), + ( crate::api::schema::IntegrationTarget::Cursor, cursor_dir().map(|dir| dir.join(super::CURSOR_HOOK_INSTALL_NAME)), super::CURSOR_INTEGRATION_VERSION, diff --git a/src/integration/targets.rs b/src/integration/targets.rs index 375d2713..0150ccaa 100644 --- a/src/integration/targets.rs +++ b/src/integration/targets.rs @@ -20,7 +20,7 @@ use super::config_edit::{ use super::env::{ antigravity_cli_dir, claude_dir, codex_dir, copilot_dir, cursor_dir, devin_dir, droid_dir, grok_dir, hermes_dir, hermes_plugin_dir, kilo_dir, kimi_dir, mastracode_dir, omp_extension_dir, - opencode_dir, pi_extension_dir, qodercli_dir, + opencode_dir, pi_extension_dir, qodercli_dir, qwen_dir, }; use super::file_ops::{ make_executable, remove_dir_all_if_exists, remove_file_if_exists, remove_legacy_bash_hook_file, @@ -37,6 +37,7 @@ use super::types::{ KiloUninstallResult, KimiInstallPaths, KimiUninstallResult, MastracodeInstallPaths, MastracodeUninstallResult, OmpInstallPaths, OmpUninstallResult, OpenCodeInstallPaths, OpenCodeUninstallResult, PiUninstallResult, QodercliInstallPaths, QodercliUninstallResult, + QwenInstallPaths, QwenUninstallResult, }; use super::{ ANTIGRAVITY_CLI_HOOK_ASSET, ANTIGRAVITY_CLI_HOOK_BLOCK_NAME, ANTIGRAVITY_CLI_HOOK_EVENTS, @@ -55,6 +56,7 @@ use super::{ OPENCODE_PLUGIN_INSTALL_NAME, OPENCODE_TUI_PLUGIN_ASSET, OPENCODE_TUI_PLUGIN_INSTALL_NAME, OPENCODE_TUI_PLUGIN_SPEC, PI_EXTENSION_ASSET, PI_EXTENSION_INSTALL_NAME, QODERCLI_HOOK_ASSET, QODERCLI_HOOK_EVENTS, QODERCLI_HOOK_INSTALL_NAME, QODERCLI_REMOVED_LIFECYCLE_HOOK_EVENTS, + QWEN_HOOK_ASSET, QWEN_HOOK_EVENTS, QWEN_HOOK_INSTALL_NAME, }; fn ensure_extension_dir(dir: &Path, agent: &str) -> io::Result<()> { @@ -945,6 +947,59 @@ pub(crate) fn install_qodercli() -> io::Result<QodercliInstallPaths> { }) } +pub(crate) fn install_qwen() -> io::Result<QwenInstallPaths> { + let dir = qwen_dir()?; + if !dir.is_dir() { + return Err(io::Error::other(format!( + "qwen code config directory not found at {}. install qwen code first", + dir.display() + ))); + } + + let hooks_dir = dir.join("hooks"); + fs::create_dir_all(&hooks_dir)?; + + let hook_path = hooks_dir.join(QWEN_HOOK_INSTALL_NAME); + fs::write(&hook_path, QWEN_HOOK_ASSET)?; + make_executable(&hook_path)?; + + let settings_path = dir.join("settings.json"); + let mut settings = if settings_path.is_file() { + serde_json::from_str::<Value>(&fs::read_to_string(&settings_path)?).map_err(|err| { + io::Error::other(format!( + "failed to parse {}: {err}", + settings_path.display() + )) + })? + } else { + json!({}) + }; + + let hooks = ensure_hooks_object( + &mut settings, + &settings_path, + "qwen settings", + "qwen settings hooks", + )?; + for (event, action) in QWEN_HOOK_EVENTS { + remove_hook_commands(hooks, event, &hook_path, Some(action))?; + ensure_command_hook( + hooks, + event, + hook_command(&hook_path, Some(action)), + 10_000, + Some("*"), + )?; + } + + fs::write(&settings_path, serde_json::to_string_pretty(&settings)?)?; + + Ok(QwenInstallPaths { + hook_path, + settings_path, + }) +} + pub(crate) fn install_cursor() -> io::Result<CursorInstallPaths> { let dir = cursor_dir()?; if !dir.is_dir() { @@ -1047,6 +1102,46 @@ pub(crate) fn uninstall_qodercli() -> io::Result<QodercliUninstallResult> { }) } +pub(crate) fn uninstall_qwen() -> io::Result<QwenUninstallResult> { + let hook_path = qwen_dir()?.join("hooks").join(QWEN_HOOK_INSTALL_NAME); + let settings_path = qwen_dir()?.join("settings.json"); + let mut updated_settings = false; + + if settings_path.is_file() { + let mut settings = serde_json::from_str::<Value>(&fs::read_to_string(&settings_path)?) + .map_err(|err| { + io::Error::other(format!( + "failed to parse {}: {err}", + settings_path.display() + )) + })?; + + if let Some(hooks) = hooks_object_if_present( + &mut settings, + &settings_path, + "qwen settings", + "qwen settings hooks", + )? { + for (event, action) in QWEN_HOOK_EVENTS { + updated_settings |= remove_hook_commands(hooks, event, &hook_path, Some(action))?; + } + } + + if updated_settings { + fs::write(&settings_path, serde_json::to_string_pretty(&settings)?)?; + } + } + + let removed_hook_file = remove_file_if_exists(&hook_path)?; + + Ok(QwenUninstallResult { + hook_path, + settings_path, + removed_hook_file, + updated_settings, + }) +} + pub(crate) fn uninstall_cursor() -> io::Result<CursorUninstallResult> { let cursor_home = cursor_dir()?; let hook_path = cursor_home.join(CURSOR_HOOK_INSTALL_NAME); diff --git a/src/integration/tests.rs b/src/integration/tests.rs index 324ae6ce..d54183f2 100644 --- a/src/integration/tests.rs +++ b/src/integration/tests.rs @@ -101,6 +101,7 @@ fn clear_integration_path_env() { std::env::remove_var(KIMI_CODE_HOME_ENV_VAR); std::env::remove_var("XDG_CONFIG_HOME"); std::env::remove_var(QODERCLI_CONFIG_DIR_ENV_VAR); + std::env::remove_var(QWEN_HOME_ENV_VAR); std::env::remove_var(CURSOR_CONFIG_DIR_ENV_VAR); std::env::remove_var(ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR); std::env::remove_var(GROK_CONFIG_DIR_ENV_VAR); @@ -195,6 +196,7 @@ fn windows_supports_portable_integrations() { assert!(integration_target_supported(IntegrationTarget::Droid)); assert!(integration_target_supported(IntegrationTarget::Kimi)); assert!(integration_target_supported(IntegrationTarget::Qodercli)); + assert!(integration_target_supported(IntegrationTarget::Qwen)); } #[cfg(windows)] @@ -3187,6 +3189,108 @@ fn install_qodercli_errors_when_config_dir_missing() { } #[test] +fn install_qwen_writes_session_hook_and_preserves_settings() { + let _lock = integration_env_lock(); + let base = unique_base(); + let qwen_dir = base.join(".qwen"); + fs::create_dir_all(&qwen_dir).unwrap(); + fs::write( + qwen_dir.join("settings.json"), + r#"{"permissions":{"allow":["Read"]},"hooks":{}}"#, + ) + .unwrap(); + std::env::set_var(QWEN_HOME_ENV_VAR, &qwen_dir); + + let installed = install_qwen().unwrap(); + + assert_eq!( + installed.hook_path, + qwen_dir.join("hooks").join(QWEN_HOOK_INSTALL_NAME) + ); + assert_eq!(installed.settings_path, qwen_dir.join("settings.json")); + assert!(installed.hook_path.is_file()); + + let settings: Value = + serde_json::from_str(&fs::read_to_string(&installed.settings_path).unwrap()).unwrap(); + let entries = settings["hooks"]["SessionStart"].as_array().unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0]["matcher"], "*"); + assert_eq!(entries[0]["hooks"][0]["timeout"], 10_000); + let command = entries[0]["hooks"][0]["command"].as_str().unwrap(); + assert!(command.contains(QWEN_HOOK_INSTALL_NAME)); + assert!(command.ends_with("session")); + assert!(settings.get("permissions").is_some()); + let hook_asset = fs::read_to_string(&installed.hook_path).unwrap(); + assert!(hook_asset.contains("HERDR_INTEGRATION_ID=qwen")); + assert!(hook_asset.contains("HERDR_INTEGRATION_VERSION=1")); + assert!(hook_asset.contains("herdr:qwen")); + + install_qwen().unwrap(); + let settings: Value = + serde_json::from_str(&fs::read_to_string(&installed.settings_path).unwrap()).unwrap(); + assert_eq!( + settings["hooks"]["SessionStart"].as_array().unwrap().len(), + 1 + ); + + std::env::remove_var(QWEN_HOME_ENV_VAR); + let _ = fs::remove_dir_all(base); +} + +#[test] +fn uninstall_qwen_removes_only_herdr_hook() { + let _lock = integration_env_lock(); + let base = unique_base(); + let qwen_dir = base.join(".qwen"); + fs::create_dir_all(&qwen_dir).unwrap(); + std::env::set_var(QWEN_HOME_ENV_VAR, &qwen_dir); + + install_qwen().unwrap(); + let settings_path = qwen_dir.join("settings.json"); + let mut settings: Value = + serde_json::from_str(&fs::read_to_string(&settings_path).unwrap()).unwrap(); + settings["hooks"]["SessionStart"] + .as_array_mut() + .unwrap() + .push(json!({ + "matcher": "resume", + "hooks": [{"type": "command", "command": "echo user-defined"}], + })); + fs::write( + &settings_path, + serde_json::to_string_pretty(&settings).unwrap(), + ) + .unwrap(); + + let result = uninstall_qwen().unwrap(); + assert!(result.removed_hook_file); + assert!(result.updated_settings); + + let settings: Value = + serde_json::from_str(&fs::read_to_string(&settings_path).unwrap()).unwrap(); + let remaining = settings["hooks"]["SessionStart"].as_array().unwrap(); + assert_eq!(remaining.len(), 1); + assert_eq!(remaining[0]["hooks"][0]["command"], "echo user-defined"); + + std::env::remove_var(QWEN_HOME_ENV_VAR); + let _ = fs::remove_dir_all(base); +} + +#[test] +fn install_qwen_errors_when_config_dir_missing() { + let _lock = integration_env_lock(); + let base = unique_base(); + let missing = base.join(".qwen"); + std::env::set_var(QWEN_HOME_ENV_VAR, &missing); + + let err = install_qwen().unwrap_err().to_string(); + assert!(err.contains("qwen code config directory not found")); + + std::env::remove_var(QWEN_HOME_ENV_VAR); + let _ = fs::remove_dir_all(base); +} + +#[test] fn install_cursor_writes_hook_and_updates_hooks_json() { let _lock = integration_env_lock(); let base = unique_base(); diff --git a/src/integration/types.rs b/src/integration/types.rs index c5c24267..c7eaf795 100644 --- a/src/integration/types.rs +++ b/src/integration/types.rs @@ -70,6 +70,12 @@ pub(crate) struct QodercliInstallPaths { } #[derive(Debug)] +pub(crate) struct QwenInstallPaths { + pub hook_path: PathBuf, + pub settings_path: PathBuf, +} + +#[derive(Debug)] pub(crate) struct CursorInstallPaths { pub hook_path: PathBuf, pub hooks_path: PathBuf, @@ -119,6 +125,14 @@ pub(crate) struct QodercliUninstallResult { pub updated_settings: bool, } +#[derive(Debug)] +pub(crate) struct QwenUninstallResult { + pub hook_path: PathBuf, + pub settings_path: PathBuf, + pub removed_hook_file: bool, + pub updated_settings: bool, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct IntegrationStatus { pub target: crate::api::schema::IntegrationTarget, |
