use std::sync::Arc;
use astrid_capsule_install::{InstallOptions, InstallOutput, InstallPhase};
use astrid_events::kernel_api::KernelResponse;
pub(super) async fn handle_install_capsule(
kernel: &Arc<crate::Kernel>,
source: &str,
workspace: bool,
) -> KernelResponse {
if workspace {
return KernelResponse::Error(
"workspace installs are CLI-only — the daemon has no meaningful CWD; \
use a system install (drop the --workspace flag) instead"
.to_string(),
);
}
let is_remote = source.starts_with("https://")
|| source.starts_with("http://")
|| source.starts_with("github.com/")
|| source.starts_with('@')
|| source.starts_with("gh:");
if is_remote {
return KernelResponse::Error(format!(
"kernel-side install accepts only local paths; resolve '{source}' via the \
gateway registry route first (the daemon never fetches URLs)"
));
}
let path_str = source.strip_prefix("file://").unwrap_or(source);
let path = std::path::PathBuf::from(path_str);
if !path.exists() {
return KernelResponse::Error(format!("source path does not exist: {}", path.display()));
}
let home = match astrid_core::dirs::AstridHome::resolve() {
Ok(h) => h,
Err(e) => return KernelResponse::Error(format!("resolve AstridHome: {e}")),
};
let opts = InstallOptions {
workspace: false,
original_source: Some(source.to_string()),
skip_import_check: false,
lifecycle_bus: None,
};
let is_archive = path.is_file()
&& path
.extension()
.and_then(|e| e.to_str())
.is_some_and(|e| e.eq_ignore_ascii_case("capsule"));
let install_result = if is_archive {
let p = path.clone();
let h = home.clone();
tokio::task::spawn_blocking(move || {
astrid_capsule_install::unpack_and_install(&p, &h, opts)
})
.await
} else if path.is_dir() {
let p = path.clone();
let h = home.clone();
tokio::task::spawn_blocking(move || {
astrid_capsule_install::install_from_local_path(&p, &h, opts)
})
.await
} else {
return KernelResponse::Error(format!(
"source must be a directory containing Capsule.toml or a *.capsule archive: {}",
path.display()
));
};
let output = match install_result {
Ok(Ok(o)) => o,
Ok(Err(e)) => return KernelResponse::Error(format!("install failed: {e:#}")),
Err(e) => return KernelResponse::Error(format!("install task panicked: {e}")),
};
kernel.load_all_capsules().await;
KernelResponse::Success(install_output_json(&output))
}
fn install_output_json(o: &InstallOutput) -> serde_json::Value {
serde_json::json!({
"target_dir": o.target_dir.display().to_string(),
"phase": match o.phase {
InstallPhase::Install => "install",
InstallPhase::Upgrade => "upgrade",
},
"installed_version": o.installed_version,
"previous_version": o.previous_version,
"wasm_hash": o.wasm_hash,
"env_path": o.env_path.display().to_string(),
"env_needs_prompt": o.env_needs_prompt,
"missing_imports": o.missing_imports.iter().map(|m| serde_json::json!({
"namespace": m.namespace,
"interface": m.interface,
"requirement": m.requirement,
})).collect::<Vec<_>>(),
"export_conflicts": o.export_conflicts.iter().map(|c| serde_json::json!({
"interface": c.interface,
"existing_capsule": c.existing_capsule,
})).collect::<Vec<_>>(),
})
}