fix: run integration commands without server
This commit is contained in:
parent
eec56a56a9
commit
c950670dca
275
src/app/api.rs
275
src/app/api.rs
|
|
@ -276,9 +276,9 @@ impl App {
|
|||
use bytes::Bytes;
|
||||
|
||||
use crate::api::schema::{
|
||||
ErrorBody, ErrorResponse, IntegrationInstallResult, IntegrationTarget,
|
||||
IntegrationUninstallResult, Method, PaneListParams, PaneReadResult, ReadSource,
|
||||
ResponseResult, SuccessResponse, TabListParams,
|
||||
ErrorBody, ErrorResponse, IntegrationInstallResult, IntegrationUninstallResult, Method,
|
||||
PaneListParams, PaneReadResult, ReadSource, ResponseResult, SuccessResponse,
|
||||
TabListParams,
|
||||
};
|
||||
|
||||
let response = match request.method {
|
||||
|
|
@ -1252,107 +1252,17 @@ impl App {
|
|||
}
|
||||
Method::IntegrationInstall(params) => {
|
||||
let target = params.target;
|
||||
let messages = match target {
|
||||
IntegrationTarget::Pi => {
|
||||
let path = crate::integration::install_pi().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_install_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match path {
|
||||
Ok(path) => {
|
||||
crate::logging::integration_action("install", "pi", "ok");
|
||||
vec![format!("installed pi integration to {}", path.display())]
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Claude => {
|
||||
let installed = crate::integration::install_claude().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_install_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match installed {
|
||||
Ok(installed) => {
|
||||
crate::logging::integration_action("install", "claude", "ok");
|
||||
vec![
|
||||
format!(
|
||||
"installed claude integration hook to {}",
|
||||
installed.hook_path.display()
|
||||
),
|
||||
format!(
|
||||
"ensured claude settings at {}",
|
||||
installed.settings_path.display()
|
||||
),
|
||||
]
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Codex => {
|
||||
let installed = crate::integration::install_codex().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_install_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match installed {
|
||||
Ok(installed) => {
|
||||
crate::logging::integration_action("install", "codex", "ok");
|
||||
vec![
|
||||
format!(
|
||||
"installed codex integration hook to {}",
|
||||
installed.hook_path.display()
|
||||
),
|
||||
format!(
|
||||
"ensured codex hooks at {}",
|
||||
installed.hooks_path.display()
|
||||
),
|
||||
format!(
|
||||
"ensured codex config at {}",
|
||||
installed.config_path.display()
|
||||
),
|
||||
]
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Opencode => {
|
||||
let installed = crate::integration::install_opencode().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_install_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match installed {
|
||||
Ok(installed) => {
|
||||
crate::logging::integration_action("install", "opencode", "ok");
|
||||
vec![format!(
|
||||
"installed opencode integration plugin to {}",
|
||||
installed.plugin_path.display()
|
||||
)]
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
let messages = match crate::integration::install_target(target) {
|
||||
Ok(messages) => messages,
|
||||
Err(err) => {
|
||||
return serde_json::to_string(&ErrorResponse {
|
||||
id: request.id,
|
||||
error: ErrorBody {
|
||||
code: "integration_install_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1366,152 +1276,17 @@ impl App {
|
|||
}
|
||||
Method::IntegrationUninstall(params) => {
|
||||
let target = params.target;
|
||||
let messages = match target {
|
||||
IntegrationTarget::Pi => {
|
||||
let result = crate::integration::uninstall_pi().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_uninstall_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match result {
|
||||
Ok(result) => {
|
||||
crate::logging::integration_action("uninstall", "pi", "ok");
|
||||
if result.removed_extension {
|
||||
vec![format!(
|
||||
"removed pi integration extension at {}",
|
||||
result.extension_path.display()
|
||||
)]
|
||||
} else {
|
||||
vec![format!(
|
||||
"no pi integration extension found at {}",
|
||||
result.extension_path.display()
|
||||
)]
|
||||
}
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Claude => {
|
||||
let result = crate::integration::uninstall_claude().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_uninstall_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match result {
|
||||
Ok(result) => {
|
||||
crate::logging::integration_action("uninstall", "claude", "ok");
|
||||
let mut messages = Vec::new();
|
||||
if result.removed_hook_file {
|
||||
messages.push(format!(
|
||||
"removed claude hook at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no claude hook found at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
}
|
||||
if result.updated_settings {
|
||||
messages.push(format!(
|
||||
"removed herdr claude hook entries from {}",
|
||||
result.settings_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no herdr claude hook entries found in {}",
|
||||
result.settings_path.display()
|
||||
));
|
||||
}
|
||||
messages
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Codex => {
|
||||
let result = crate::integration::uninstall_codex().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_uninstall_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match result {
|
||||
Ok(result) => {
|
||||
crate::logging::integration_action("uninstall", "codex", "ok");
|
||||
let mut messages = Vec::new();
|
||||
if result.removed_hook_file {
|
||||
messages.push(format!(
|
||||
"removed codex hook at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no codex hook found at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
}
|
||||
if result.updated_hooks {
|
||||
messages.push(format!(
|
||||
"removed herdr codex hook entries from {}",
|
||||
result.hooks_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no herdr codex hook entries found in {}",
|
||||
result.hooks_path.display()
|
||||
));
|
||||
}
|
||||
messages.push(format!(
|
||||
"left codex config unchanged at {}",
|
||||
result.config_path.display()
|
||||
));
|
||||
messages
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
}
|
||||
IntegrationTarget::Opencode => {
|
||||
let result = crate::integration::uninstall_opencode().map_err(|err| {
|
||||
serde_json::to_string(&ErrorResponse {
|
||||
id: request.id.clone(),
|
||||
error: ErrorBody {
|
||||
code: "integration_uninstall_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
match result {
|
||||
Ok(result) => {
|
||||
crate::logging::integration_action("uninstall", "opencode", "ok");
|
||||
if result.removed_plugin {
|
||||
vec![format!(
|
||||
"removed opencode integration plugin at {}",
|
||||
result.plugin_path.display()
|
||||
)]
|
||||
} else {
|
||||
vec![format!(
|
||||
"no opencode integration plugin found at {}",
|
||||
result.plugin_path.display()
|
||||
)]
|
||||
}
|
||||
}
|
||||
Err(response) => return response,
|
||||
}
|
||||
let messages = match crate::integration::uninstall_target(target) {
|
||||
Ok(messages) => messages,
|
||||
Err(err) => {
|
||||
return serde_json::to_string(&ErrorResponse {
|
||||
id: request.id,
|
||||
error: ErrorBody {
|
||||
code: "integration_uninstall_failed".into(),
|
||||
message: err.to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
67
src/cli.rs
67
src/cli.rs
|
|
@ -6,12 +6,11 @@ use serde::Serialize;
|
|||
|
||||
use crate::api;
|
||||
use crate::api::schema::{
|
||||
AgentStatus, EmptyParams, IntegrationInstallParams, IntegrationTarget,
|
||||
IntegrationUninstallParams, Method, OutputMatch, PaneListParams, PaneReadParams,
|
||||
PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneTarget,
|
||||
PaneWaitForOutputParams, PingParams, ReadSource, Request, SplitDirection, Subscription,
|
||||
TabCreateParams, TabListParams, TabRenameParams, TabTarget, WorkspaceCreateParams,
|
||||
WorkspaceRenameParams, WorkspaceTarget,
|
||||
AgentStatus, EmptyParams, IntegrationTarget, Method, OutputMatch, PaneListParams,
|
||||
PaneReadParams, PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams,
|
||||
PaneTarget, PaneWaitForOutputParams, PingParams, ReadSource, Request, SplitDirection,
|
||||
Subscription, TabCreateParams, TabListParams, TabRenameParams, TabTarget,
|
||||
WorkspaceCreateParams, WorkspaceRenameParams, WorkspaceTarget,
|
||||
};
|
||||
|
||||
pub enum CommandOutcome {
|
||||
|
|
@ -962,26 +961,16 @@ fn integration_install(args: &[String]) -> std::io::Result<i32> {
|
|||
return Ok(2);
|
||||
};
|
||||
|
||||
let response = send_request(&Request {
|
||||
id: "cli:integration:install".into(),
|
||||
method: Method::IntegrationInstall(IntegrationInstallParams { target }),
|
||||
})?;
|
||||
|
||||
if let Some(error) = response.get("error") {
|
||||
eprintln!("{}", serde_json::to_string(error).unwrap());
|
||||
return Ok(1);
|
||||
match crate::integration::install_target(target) {
|
||||
Ok(messages) => {
|
||||
print_integration_messages(messages);
|
||||
Ok(0)
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
let Some(messages) = response["result"]["details"]["messages"].as_array() else {
|
||||
eprintln!("invalid integration install response");
|
||||
return Ok(1);
|
||||
};
|
||||
|
||||
for message in messages.iter().filter_map(|entry| entry.as_str()) {
|
||||
println!("{message}");
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn integration_uninstall(args: &[String]) -> std::io::Result<i32> {
|
||||
|
|
@ -989,26 +978,22 @@ fn integration_uninstall(args: &[String]) -> std::io::Result<i32> {
|
|||
return Ok(2);
|
||||
};
|
||||
|
||||
let response = send_request(&Request {
|
||||
id: "cli:integration:uninstall".into(),
|
||||
method: Method::IntegrationUninstall(IntegrationUninstallParams { target }),
|
||||
})?;
|
||||
|
||||
if let Some(error) = response.get("error") {
|
||||
eprintln!("{}", serde_json::to_string(error).unwrap());
|
||||
return Ok(1);
|
||||
match crate::integration::uninstall_target(target) {
|
||||
Ok(messages) => {
|
||||
print_integration_messages(messages);
|
||||
Ok(0)
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(messages) = response["result"]["details"]["messages"].as_array() else {
|
||||
eprintln!("invalid integration uninstall response");
|
||||
return Ok(1);
|
||||
};
|
||||
|
||||
for message in messages.iter().filter_map(|entry| entry.as_str()) {
|
||||
fn print_integration_messages(messages: Vec<String>) {
|
||||
for message in messages {
|
||||
println!("{message}");
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn parse_integration_target(
|
||||
|
|
|
|||
|
|
@ -69,6 +69,159 @@ pub(crate) fn apply_pane_env(cmd: &mut CommandBuilder, pane_id: PaneId) {
|
|||
cmd.env(HERDR_PANE_ID_ENV_VAR, format!("p_{}", pane_id.raw()));
|
||||
}
|
||||
|
||||
pub(crate) fn install_target(
|
||||
target: crate::api::schema::IntegrationTarget,
|
||||
) -> io::Result<Vec<String>> {
|
||||
let messages = match target {
|
||||
crate::api::schema::IntegrationTarget::Pi => {
|
||||
let path = install_pi()?;
|
||||
vec![format!("installed pi integration to {}", path.display())]
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Claude => {
|
||||
let installed = install_claude()?;
|
||||
vec![
|
||||
format!(
|
||||
"installed claude integration hook to {}",
|
||||
installed.hook_path.display()
|
||||
),
|
||||
format!(
|
||||
"ensured claude settings at {}",
|
||||
installed.settings_path.display()
|
||||
),
|
||||
]
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Codex => {
|
||||
let installed = install_codex()?;
|
||||
vec![
|
||||
format!(
|
||||
"installed codex integration hook to {}",
|
||||
installed.hook_path.display()
|
||||
),
|
||||
format!("ensured codex hooks at {}", installed.hooks_path.display()),
|
||||
format!(
|
||||
"ensured codex config at {}",
|
||||
installed.config_path.display()
|
||||
),
|
||||
]
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Opencode => {
|
||||
let installed = install_opencode()?;
|
||||
vec![format!(
|
||||
"installed opencode integration plugin to {}",
|
||||
installed.plugin_path.display()
|
||||
)]
|
||||
}
|
||||
};
|
||||
|
||||
crate::logging::integration_action("install", integration_target_label(target), "ok");
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub(crate) fn uninstall_target(
|
||||
target: crate::api::schema::IntegrationTarget,
|
||||
) -> io::Result<Vec<String>> {
|
||||
let messages = match target {
|
||||
crate::api::schema::IntegrationTarget::Pi => {
|
||||
let result = uninstall_pi()?;
|
||||
if result.removed_extension {
|
||||
vec![format!(
|
||||
"removed pi integration extension at {}",
|
||||
result.extension_path.display()
|
||||
)]
|
||||
} else {
|
||||
vec![format!(
|
||||
"no pi integration extension found at {}",
|
||||
result.extension_path.display()
|
||||
)]
|
||||
}
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Claude => {
|
||||
let result = uninstall_claude()?;
|
||||
let mut messages = Vec::new();
|
||||
if result.removed_hook_file {
|
||||
messages.push(format!(
|
||||
"removed claude hook at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no claude hook found at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
}
|
||||
if result.updated_settings {
|
||||
messages.push(format!(
|
||||
"removed herdr claude hook entries from {}",
|
||||
result.settings_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no herdr claude hook entries found in {}",
|
||||
result.settings_path.display()
|
||||
));
|
||||
}
|
||||
messages
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Codex => {
|
||||
let result = uninstall_codex()?;
|
||||
let mut messages = Vec::new();
|
||||
if result.removed_hook_file {
|
||||
messages.push(format!(
|
||||
"removed codex hook at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no codex hook found at {}",
|
||||
result.hook_path.display()
|
||||
));
|
||||
}
|
||||
if result.updated_hooks {
|
||||
messages.push(format!(
|
||||
"removed herdr codex hook entries from {}",
|
||||
result.hooks_path.display()
|
||||
));
|
||||
} else {
|
||||
messages.push(format!(
|
||||
"no herdr codex hook entries found in {}",
|
||||
result.hooks_path.display()
|
||||
));
|
||||
}
|
||||
messages.push(format!(
|
||||
"left codex config unchanged at {}",
|
||||
result.config_path.display()
|
||||
));
|
||||
messages
|
||||
}
|
||||
crate::api::schema::IntegrationTarget::Opencode => {
|
||||
let result = uninstall_opencode()?;
|
||||
if result.removed_plugin {
|
||||
vec![format!(
|
||||
"removed opencode integration plugin at {}",
|
||||
result.plugin_path.display()
|
||||
)]
|
||||
} else {
|
||||
vec![format!(
|
||||
"no opencode integration plugin found at {}",
|
||||
result.plugin_path.display()
|
||||
)]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
crate::logging::integration_action("uninstall", integration_target_label(target), "ok");
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn integration_target_label(target: crate::api::schema::IntegrationTarget) -> &'static str {
|
||||
match target {
|
||||
crate::api::schema::IntegrationTarget::Pi => "pi",
|
||||
crate::api::schema::IntegrationTarget::Claude => "claude",
|
||||
crate::api::schema::IntegrationTarget::Codex => "codex",
|
||||
crate::api::schema::IntegrationTarget::Opencode => "opencode",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn install_pi() -> io::Result<PathBuf> {
|
||||
let dir = pi_extension_dir()?;
|
||||
if !dir.is_dir() {
|
||||
|
|
|
|||
|
|
@ -796,7 +796,7 @@ fn named_sessions_use_separate_servers_and_workspace_state() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn integration_commands_honor_socket_override_when_server_is_missing() {
|
||||
fn integration_commands_run_locally_when_server_is_missing() {
|
||||
let base = unique_test_dir();
|
||||
let home_dir = base.join("home");
|
||||
let extensions_dir = home_dir.join(".pi/agent/extensions");
|
||||
|
|
@ -827,10 +827,10 @@ fn integration_commands_honor_socket_override_when_server_is_missing() {
|
|||
.env("HOME", &home_dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(integration_install.status.code(), Some(1));
|
||||
assert_eq!(integration_install.status.code(), Some(0));
|
||||
assert!(
|
||||
!expected_extension.exists(),
|
||||
"integration install should not run local install logic when socket is missing"
|
||||
expected_extension.exists(),
|
||||
"integration install should write local files without a server"
|
||||
);
|
||||
|
||||
let integration_uninstall = Command::new(env!("CARGO_BIN_EXE_herdr"))
|
||||
|
|
@ -839,10 +839,10 @@ fn integration_commands_honor_socket_override_when_server_is_missing() {
|
|||
.env("HOME", &home_dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(integration_uninstall.status.code(), Some(1));
|
||||
assert_eq!(integration_uninstall.status.code(), Some(0));
|
||||
assert!(
|
||||
!expected_extension.exists(),
|
||||
"integration uninstall should also be socket-backed when socket is missing"
|
||||
"integration uninstall should remove local files without a server"
|
||||
);
|
||||
|
||||
cleanup_test_base(&base);
|
||||
|
|
|
|||
Loading…
Reference in New Issue