remem/install/host.rs
1use anyhow::{bail, Result};
2use clap::ValueEnum;
3use std::path::PathBuf;
4
5/// Which host(s) to (un)install into.
6#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
7pub enum InstallTarget {
8 /// Install to every host whose config directory already exists.
9 Auto,
10 /// Install only to Claude Code (~/.claude.json + ~/.claude/settings.json).
11 Claude,
12 /// Install only to Codex (~/.codex/config.toml).
13 Codex,
14 /// Install only to Cursor (~/.cursor/hooks.json + ~/.cursor/mcp.json).
15 Cursor,
16 /// Install to every known host, creating config files if missing.
17 All,
18}
19
20/// Outcome of a hook installation attempt.
21pub enum HookSupport {
22 /// Hooks were installed.
23 Installed,
24 /// Host does not support hooks; reason for user-visible log.
25 /// Retained for future hosts (e.g. Cursor) that may not expose a hook
26 /// system with the same shape as Claude/Codex.
27 #[allow(dead_code)]
28 Skipped(&'static str),
29}
30
31pub struct HookRepairReport {
32 pub path: PathBuf,
33 pub registered: usize,
34 pub expected: usize,
35 pub mcp_warning: Option<String>,
36 pub scope_warning: Option<String>,
37}
38
39/// A host capable of running the remem MCP server.
40///
41/// Each host owns its own config file format and mutation logic. The runtime
42/// layer only orchestrates which hosts to touch.
43pub trait InstallHost {
44 /// Short identifier printed in logs (e.g. "claude", "codex").
45 fn name(&self) -> &'static str;
46
47 /// Path to the primary config file this host manages.
48 fn config_path(&self) -> PathBuf;
49
50 /// True when the host appears to be installed on this machine.
51 /// Used by `InstallTarget::Auto` to decide whether to touch it.
52 fn is_available(&self) -> bool;
53
54 /// Add / update the remem MCP server entry. Idempotent.
55 fn install_mcp(&self, bin: &str) -> Result<()>;
56
57 /// Remove any remem MCP server entry. Idempotent.
58 fn uninstall_mcp(&self, bin: &str) -> Result<()>;
59
60 /// Add / update remem hooks. Hosts without hook support return `Skipped`.
61 fn install_hooks(&self, bin: &str) -> Result<HookSupport>;
62
63 /// Repair host hooks without touching MCP, runtime store, or tokens.
64 fn repair_hooks(&self, _bin: &str) -> Result<HookRepairReport> {
65 bail!("{} hook repair is not supported", self.name())
66 }
67
68 /// Remove remem hooks. No-op if the host doesn't support hooks.
69 fn uninstall_hooks(&self, bin: &str) -> Result<()>;
70
71 /// Describe the writes a real install would do, without touching disk.
72 /// Returned lines are printed verbatim in dry-run mode.
73 fn dry_run_plan(&self, bin: &str) -> Vec<String>;
74}