remem-ai 0.5.11

Persistent memory for Claude Code and Codex — single binary, automatic context
Documentation
use anyhow::{bail, Result};

use crate::install::duplicates::{format_warning_lines, inspect_install_paths};
use crate::install::host::{HookSupport, InstallTarget};
use crate::install::hosts::resolve_hosts;
use crate::install::paths::{binary_path, old_hooks_path, remem_data_dir};

pub fn install(target: InstallTarget, dry_run: bool) -> Result<()> {
    let bin = binary_path()?;
    let hosts = resolve_hosts(target);
    if hosts.is_empty() {
        bail!(
            "没检测到可用的 host(target=Auto 时仅安装已检测到的 host)。\n\
             如需强制安装到全部 host,请使用 `--target all`。"
        );
    }

    if dry_run {
        eprintln!("remem install (dry-run) — 以下写入不会被执行:");
        for host in &hosts {
            eprintln!("{}", host.name());
            for line in host.dry_run_plan(&bin) {
                eprintln!("{line}");
            }
        }
        eprintln!(
            "  config -> {} (memory_ai host/profile defaults)",
            crate::runtime_config::config_path().display()
        );
        eprintln!("  data   -> {}", remem_data_dir().display());
        print_install_path_warnings(&bin);
        return Ok(());
    }

    eprintln!("remem install:");
    let runtime_hosts = hosts
        .iter()
        .map(|host| runtime_host_name(host.name()))
        .collect::<Vec<_>>();
    let config_path = crate::runtime_config::ensure_config_for_hosts(&runtime_hosts)?;
    eprintln!("  config -> {}", config_path.display());
    for host in &hosts {
        eprintln!("{}", host.name());
        host.install_mcp(&bin)?;
        eprintln!("  MCP    -> {}", host.config_path().display());
        match host.install_hooks(&bin)? {
            HookSupport::Installed => eprintln!("  hooks  ✓"),
            HookSupport::Skipped(reason) => eprintln!("  hooks  skipped: {reason}"),
        }
    }

    let data_dir = remem_data_dir();
    std::fs::create_dir_all(&data_dir)?;
    eprintln!("  data   -> {}", data_dir.display());
    let api_token_path = crate::api::ensure_api_token()?;
    eprintln!("  API    -> token {}", api_token_path.display());
    eprintln!("  binary -> {}", bin);
    print_install_path_warnings(&bin);

    let old_path = old_hooks_path();
    if old_path.exists() {
        eprintln!();
        eprintln!("Legacy hooks.json detected: {}", old_path.display());
        eprintln!(
            "Claude Code does not read this file. Safe to delete: rm {}",
            old_path.display()
        );
    }

    eprintln!();
    eprintln!("Next steps:");
    eprintln!("  1. Restart the affected host(s) (Claude Code / Codex)");
    eprintln!("  2. remem will automatically capture your sessions (hosts with hook support)");
    eprintln!("  3. Run 'remem status' to check system health");

    Ok(())
}

fn print_install_path_warnings(bin: &str) {
    let report = inspect_install_paths(Some(std::path::Path::new(bin)));
    let lines = format_warning_lines(&report);
    if lines.is_empty() {
        return;
    }

    eprintln!();
    eprintln!("Install path warning:");
    for line in lines {
        eprintln!("{line}");
    }
}

fn runtime_host_name(install_host: &str) -> &'static str {
    match install_host {
        "claude" => crate::runtime_config::CLAUDE_HOST,
        "codex" => crate::runtime_config::CODEX_HOST,
        _ => "unknown",
    }
}

pub fn uninstall(target: InstallTarget, dry_run: bool) -> Result<()> {
    let bin = binary_path()?;
    // Uninstall defaults to "all known hosts" so a stale config isn't left
    // behind if the user removed a host before running uninstall.
    let effective = if matches!(target, InstallTarget::Auto) {
        InstallTarget::All
    } else {
        target
    };
    let hosts = resolve_hosts(effective);

    if dry_run {
        eprintln!("remem uninstall (dry-run) — 以下删除不会被执行:");
        for host in &hosts {
            eprintln!("{}: 移除 {}", host.name(), host.config_path().display());
        }
        return Ok(());
    }

    for host in &hosts {
        host.uninstall_mcp(&bin)?;
        host.uninstall_hooks(&bin)?;
        eprintln!(
            "  {} 已清理 ({})",
            host.name(),
            host.config_path().display()
        );
    }

    eprintln!("remem uninstall 完成");
    eprintln!("  数据目录 {} 保留不动", remem_data_dir().display());

    Ok(())
}