use crate::cli::output::{OutputConfig, OutputFormat};
use crate::cli::SupervisionCommands;
use crate::config;
use crate::error::{OlError, ERR_INVALID_CONFIG};
use crate::supervision::{select_supervisor, SupervisionMode, SupervisorKind};
pub fn run(cmd: &SupervisionCommands, output: &OutputConfig) -> Result<(), OlError> {
match cmd {
SupervisionCommands::Install => run_install(output),
SupervisionCommands::Uninstall => run_uninstall(output),
SupervisionCommands::Status => run_status(output),
SupervisionCommands::Enable => run_install(output),
SupervisionCommands::Disable => run_disable(output),
}
}
fn backend_label(kind: &SupervisorKind) -> &'static str {
match kind {
SupervisorKind::Launchd => "launchd",
SupervisorKind::Systemd => "systemd",
SupervisorKind::TaskScheduler => "task_scheduler",
SupervisorKind::None => "none",
}
}
fn mode_label(mode: &SupervisionMode) -> &'static str {
match mode {
SupervisionMode::Active => "active",
SupervisionMode::Deferred => "deferred",
SupervisionMode::Disabled => "disabled",
}
}
fn ensure_config_path() -> Result<std::path::PathBuf, OlError> {
let path = config::openlatch_dir().join("config.toml");
if !path.exists() {
config::ensure_config(config::Config::defaults().port)?;
}
Ok(path)
}
fn run_install(output: &OutputConfig) -> Result<(), OlError> {
let config_path = ensure_config_path()?;
let Some(supervisor) = select_supervisor() else {
let reason = "unsupported_os";
config::persist_supervision_state(
&config_path,
&SupervisionMode::Deferred,
&SupervisorKind::None,
Some(reason),
)?;
emit_outcome(output, "deferred", "none", Some(reason));
return Ok(());
};
let exe_path =
std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("openlatch"));
let kind = supervisor.kind();
let (mode, reason): (SupervisionMode, Option<String>) = match supervisor.install(&exe_path) {
Ok(()) => (SupervisionMode::Active, None),
Err(e) => {
let reason = format!("{} ({})", e.message, e.code);
(SupervisionMode::Deferred, Some(reason))
}
};
config::persist_supervision_state(&config_path, &mode, &kind, reason.as_deref())?;
crate::telemetry::capture_global(crate::telemetry::Event::supervision_installed(
backend_label(&kind),
mode_label(&mode),
reason.as_deref(),
));
emit_outcome(
output,
mode_label(&mode),
backend_label(&kind),
reason.as_deref(),
);
Ok(())
}
fn run_uninstall(output: &OutputConfig) -> Result<(), OlError> {
let config_path = ensure_config_path()?;
if let Some(supervisor) = select_supervisor() {
let _ = supervisor.uninstall();
}
config::persist_supervision_state(
&config_path,
&SupervisionMode::Disabled,
&SupervisorKind::None,
Some("user_uninstalled"),
)?;
emit_outcome(output, "disabled", "none", Some("user_uninstalled"));
Ok(())
}
fn run_disable(output: &OutputConfig) -> Result<(), OlError> {
let config_path = ensure_config_path()?;
if let Some(supervisor) = select_supervisor() {
let _ = supervisor.uninstall();
}
config::persist_supervision_state(
&config_path,
&SupervisionMode::Disabled,
&SupervisorKind::None,
Some("user_opt_out"),
)?;
emit_outcome(output, "disabled", "none", Some("user_opt_out"));
Ok(())
}
fn run_status(output: &OutputConfig) -> Result<(), OlError> {
let cfg = config::Config::load(None, None, false)?;
let (installed, running, description) = match select_supervisor() {
Some(sup) => match sup.status() {
Ok(s) => (s.installed, s.running, s.description),
Err(e) => {
return Err(OlError::new(
ERR_INVALID_CONFIG,
format!("Cannot query supervisor: {}", e.message),
))
}
},
None => (false, false, "unsupported OS".to_string()),
};
let configured_mode = mode_label(&cfg.supervision.mode);
let configured_backend = backend_label(&cfg.supervision.backend);
if output.format == OutputFormat::Json {
let json = serde_json::json!({
"mode": configured_mode,
"backend": configured_backend,
"installed": installed,
"running": running,
"description": description,
"disabled_reason": cfg.supervision.disabled_reason,
});
output.print_json(&json);
} else if !output.quiet {
crate::cli::header::print(output, &["supervision"]);
eprintln!(" Mode: {configured_mode}");
eprintln!(" Backend: {configured_backend}");
eprintln!(" Installed: {installed}");
eprintln!(" Running: {running}");
eprintln!(" Description: {description}");
if let Some(reason) = &cfg.supervision.disabled_reason {
eprintln!(" Reason: {reason}");
}
}
Ok(())
}
fn emit_outcome(output: &OutputConfig, mode: &str, backend: &str, reason: Option<&str>) {
if output.format == OutputFormat::Json {
let json = serde_json::json!({
"mode": mode,
"backend": backend,
"disabled_reason": reason,
});
output.print_json(&json);
} else if !output.quiet {
match mode {
"active" => output.print_step(&format!(
"Supervision installed ({backend}) — daemon will auto-start"
)),
"deferred" => {
let tail = reason.unwrap_or("install failed");
output.print_step(&format!("Supervision deferred — {tail}"));
}
"disabled" => {
let tail = reason.unwrap_or("user_opt_out");
output.print_step(&format!("Supervision disabled ({tail})"));
}
_ => output.print_step(&format!("Supervision: {mode}")),
}
}
}