remem-ai 0.3.8

Persistent memory for Claude Code — single binary, zero subprocesses
Documentation
use anyhow::{Context, Result};
use serde_json::json;

use crate::install::config::{build_hooks, build_mcp_server, remove_remem_hooks, remove_remem_mcp};
use crate::install::json_io::{read_json_file, write_json_file};
use crate::install::paths::{
    binary_path, claude_json_path, old_hooks_path, remem_data_dir, settings_path,
};

pub fn install() -> Result<()> {
    let bin = binary_path()?;
    let settings_file = settings_path();
    let claude_json_file = claude_json_path();

    let mut settings = read_json_file(&settings_file)?;
    remove_remem_hooks(&mut settings, &bin);
    remove_remem_mcp(&mut settings, &bin);

    let new_hooks = build_hooks(&bin);
    let obj = settings
        .as_object_mut()
        .context("settings.json 根节点不是 Object")?;
    let hooks = obj.entry("hooks").or_insert_with(|| json!({}));
    if let (Some(existing), Some(new)) = (hooks.as_object_mut(), new_hooks.as_object()) {
        for (event_type, entries) in new {
            let arr = existing.entry(event_type).or_insert_with(|| json!([]));
            if let (Some(arr), Some(new_entries)) = (arr.as_array_mut(), entries.as_array()) {
                for entry in new_entries {
                    arr.push(entry.clone());
                }
            }
        }
    }
    write_json_file(&settings_file, &settings)?;

    let mut claude_json = read_json_file(&claude_json_file)?;
    remove_remem_mcp(&mut claude_json, &bin);

    let obj = claude_json
        .as_object_mut()
        .context("~/.claude.json 根节点不是 Object")?;
    let mcp_servers = obj.entry("mcpServers").or_insert_with(|| json!({}));
    if let Some(servers) = mcp_servers.as_object_mut() {
        servers.insert("remem".to_string(), build_mcp_server(&bin));
    }
    write_json_file(&claude_json_file, &claude_json)?;

    let data_dir = remem_data_dir();
    std::fs::create_dir_all(&data_dir)?;

    eprintln!("remem install complete:");
    eprintln!("  hooks  -> {}", settings_file.display());
    eprintln!("  MCP    -> {}", claude_json_file.display());
    eprintln!("  data   -> {}", data_dir.display());
    eprintln!("  binary -> {}", 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 Claude Code (quit and reopen)");
    eprintln!("  2. remem will automatically capture your sessions");
    eprintln!("  3. Run 'remem status' to check system health");

    Ok(())
}

pub fn uninstall() -> Result<()> {
    let bin = binary_path()?;
    let settings_file = settings_path();
    let claude_json_file = claude_json_path();

    if settings_file.exists() {
        let mut settings = read_json_file(&settings_file)?;
        remove_remem_hooks(&mut settings, &bin);
        remove_remem_mcp(&mut settings, &bin);
        write_json_file(&settings_file, &settings)?;
        eprintln!("  hooks 已从 {} 移除", settings_file.display());
    }

    if claude_json_file.exists() {
        let mut claude_json = read_json_file(&claude_json_file)?;
        remove_remem_mcp(&mut claude_json, &bin);
        write_json_file(&claude_json_file, &claude_json)?;
        eprintln!("  MCP 已从 {} 移除", claude_json_file.display());
    }

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

    Ok(())
}