77 lines
2.7 KiB
Rust
77 lines
2.7 KiB
Rust
//! First-start hook auto-install (#106).
|
|
//!
|
|
//! The daemon runs [`auto_install`] once at startup: when the user's
|
|
//! `~/.claude/settings.json` carries no maestro hook yet (and
|
|
//! `[hooks].auto_install` hasn't opted out — checked by the caller), the same
|
|
//! installer `maestro hooks install` uses writes it. Best-effort: a failure is
|
|
//! warned about and never blocks startup. The same global hook serves every
|
|
//! Claude Code session on the machine, so nothing here targets repositories
|
|
//! or worktrees.
|
|
|
|
use std::path::Path;
|
|
|
|
/// Install the Claude Code activity hook for `home_dir` unless one is already
|
|
/// present or the home dir is unresolvable (`""`). Returns what happened.
|
|
pub fn auto_install(home_dir: &str) -> AutoInstallOutcome {
|
|
if home_dir.is_empty() {
|
|
return AutoInstallOutcome::Skipped;
|
|
}
|
|
let settings = maestro_hooks::settings_path(Path::new(home_dir));
|
|
if maestro_hooks::is_installed(&settings) {
|
|
return AutoInstallOutcome::AlreadyPresent;
|
|
}
|
|
match maestro_hooks::install_for_home(Path::new(home_dir)) {
|
|
Ok(_) => AutoInstallOutcome::Installed,
|
|
Err(e) => {
|
|
tracing::warn!(error = %e, "hooks auto-install failed (non-fatal)");
|
|
AutoInstallOutcome::Failed
|
|
}
|
|
}
|
|
}
|
|
|
|
/// What [`auto_install`] did, so callers can log and tests can assert.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum AutoInstallOutcome {
|
|
Installed,
|
|
AlreadyPresent,
|
|
Failed,
|
|
Skipped,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// Everything happens under a temp `$HOME`; the developer's own
|
|
/// `~/.claude/settings.json` is never read or written.
|
|
#[test]
|
|
fn installs_when_settings_carry_no_maestro_hook() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let outcome = auto_install(dir.path().to_str().unwrap());
|
|
assert_eq!(outcome, AutoInstallOutcome::Installed);
|
|
let settings = dir.path().join(".claude/settings.json");
|
|
assert!(maestro_hooks::is_installed(&settings));
|
|
}
|
|
|
|
#[test]
|
|
fn second_start_is_a_noop() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let home = dir.path().to_str().unwrap();
|
|
auto_install(home);
|
|
// A foreign edit between starts survives; presence alone skips.
|
|
let settings = dir.path().join(".claude/settings.json");
|
|
let before = std::fs::read_to_string(&settings).unwrap();
|
|
assert_eq!(auto_install(home), AutoInstallOutcome::AlreadyPresent);
|
|
assert_eq!(
|
|
before,
|
|
std::fs::read_to_string(&settings).unwrap(),
|
|
"already-installed settings must not be rewritten"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unresolvable_home_is_skipped() {
|
|
assert_eq!(auto_install(""), AutoInstallOutcome::Skipped);
|
|
}
|
|
}
|