remem-ai 0.3.8

Persistent memory for Claude Code — single binary, zero subprocesses
Documentation
use anyhow::Result;
use serde::Deserialize;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[derive(Debug, Deserialize)]
pub(super) struct SummarizeInput {
    pub session_id: Option<String>,
    pub cwd: Option<String>,
    pub transcript_path: Option<String>,
    pub last_assistant_message: Option<String>,
}

pub(super) fn hash_message(msg: &str) -> String {
    let mut hasher = DefaultHasher::new();
    msg.hash(&mut hasher);
    format!("{:016x}", hasher.finish())
}

pub(super) fn extract_last_assistant_message(transcript_path: &str) -> Option<String> {
    let content = std::fs::read_to_string(transcript_path).ok()?;

    for line in content.lines().rev() {
        let val: serde_json::Value = match serde_json::from_str(line) {
            Ok(value) => value,
            Err(_) => continue,
        };
        if val["type"].as_str() != Some("assistant") {
            continue;
        }
        let Some(content_arr) = val["message"]["content"].as_array() else {
            continue;
        };
        let text_parts: Vec<&str> = content_arr
            .iter()
            .filter_map(|entry| {
                if entry["type"].as_str() == Some("text") {
                    entry["text"].as_str()
                } else {
                    None
                }
            })
            .collect();
        if !text_parts.is_empty() {
            return Some(text_parts.join("\n"));
        }
    }

    None
}

pub(super) fn read_stdin_with_timeout(timeout_ms: u64) -> Result<Option<String>> {
    use std::sync::mpsc;
    use std::time::Duration;

    let (tx, rx) = mpsc::sync_channel(1);
    std::thread::spawn(move || {
        let input = std::io::read_to_string(std::io::stdin());
        let _ = tx.send(input);
    });

    match rx.recv_timeout(Duration::from_millis(timeout_ms)) {
        Ok(Ok(input)) => {
            if input.trim().is_empty() {
                Ok(None)
            } else {
                Ok(Some(input))
            }
        }
        Ok(Err(err)) => Err(err.into()),
        Err(mpsc::RecvTimeoutError::Timeout) => {
            crate::log::warn(
                "summarize",
                &format!("stdin read timed out after {}ms, skipping", timeout_ms),
            );
            Ok(None)
        }
        Err(mpsc::RecvTimeoutError::Disconnected) => {
            crate::log::warn("summarize", "stdin reader disconnected, skipping");
            Ok(None)
        }
    }
}