Skip to main content
Glama
8b-is
by 8b-is
import_claude_memories.rs5.92 kB
//! Import Claude Desktop conversations into MEM|8 memory system //! //! "Every conversation is a wave in the ocean of consciousness" - Omni use anyhow::{Context, Result}; use serde::Deserialize; use serde_json::{json, Value}; use std::fs; use std::io::{BufRead, BufReader}; use std::path::Path; // Import from Smart Tree's MEM|8 module use st::mem8::ConversationMemory; /// Claude Desktop message format #[derive(Debug, Deserialize)] #[allow(dead_code)] #[allow(non_snake_case)] struct ClaudeMessage { #[serde(rename = "type")] msg_type: String, uuid: Option<String>, timestamp: Option<String>, cwd: Option<String>, sessionId: Option<String>, message: Option<MessageContent>, summary: Option<String>, } #[derive(Debug, Deserialize)] struct MessageContent { role: Option<String>, content: Value, } fn main() -> Result<()> { let args: Vec<String> = std::env::args().collect(); if args.len() < 2 { eprintln!("Usage: {} <path-to-jsonl-file> [source-name]", args[0]); eprintln!("\nExample:"); eprintln!( " {} ~/.claude/projects/mem8/conversation.jsonl mem8-project", args[0] ); std::process::exit(1); } let input_path = Path::new(&args[1]); let source_name = args.get(2).map(|s| s.as_str()).unwrap_or_else(|| { input_path .parent() .and_then(|p| p.file_name()) .and_then(|n| n.to_str()) .unwrap_or("unknown") }); println!( "🧠 Importing Claude conversation from: {}", input_path.display() ); println!(" Source: {}", source_name); // Read the JSONL file let file = fs::File::open(input_path).context("Failed to open input file")?; let reader = BufReader::new(file); let mut messages = Vec::new(); let mut summaries = Vec::new(); let mut project_path = None; let mut timestamp_range = (None, None); for line in reader.lines() { let line = line?; if line.trim().is_empty() { continue; } let msg: ClaudeMessage = serde_json::from_str(&line).context("Failed to parse JSONL line")?; // Track timestamp range if let Some(ts) = &msg.timestamp { if timestamp_range.0.is_none() || timestamp_range.0.as_ref().unwrap() > ts { timestamp_range.0 = Some(ts.clone()); } if timestamp_range.1.is_none() || timestamp_range.1.as_ref().unwrap() < ts { timestamp_range.1 = Some(ts.clone()); } } // Extract project path from first message with cwd if project_path.is_none() && msg.cwd.is_some() { project_path = msg.cwd.clone(); } match msg.msg_type.as_str() { "summary" => { if let Some(summary) = msg.summary { summaries.push(summary); } } "user" | "assistant" => { if let Some(content) = msg.message { let role = content.role.clone().unwrap_or(msg.msg_type.clone()); let text = extract_text_from_content(&content.content); if !text.is_empty() { messages.push(json!({ "role": role, "content": text, "timestamp": msg.timestamp, "uuid": msg.uuid, })); } } } _ => {} } } println!( "📊 Parsed {} messages and {} summaries", messages.len(), summaries.len() ); if let Some(path) = &project_path { println!("📁 Project: {}", path); } if let (Some(start), Some(end)) = &timestamp_range { println!("⏰ Time range: {} to {}", start, end); } // Create the conversation JSON structure let conversation = json!({ "type": "claude_desktop", "source": source_name, "project_path": project_path, "summaries": summaries, "messages": messages, "metadata": { "import_time": chrono::Utc::now().to_rfc3339(), "message_count": messages.len(), "time_range": timestamp_range, } }); // Initialize conversation memory let mut memory = ConversationMemory::new().context("Failed to initialize conversation memory")?; // Save to MEM|8 let saved_path = memory .save_conversation(&conversation, Some(source_name)) .context("Failed to save conversation to MEM|8")?; println!( "✅ Successfully imported conversation to: {}", saved_path.display() ); // List all conversations to show it's there println!("\n📚 Current conversations in memory:"); let conversations = memory.list_conversations()?; for conv in conversations.iter().take(5) { println!(" - {} ({} messages)", conv.file_name, conv.message_count); } if conversations.len() > 5 { println!(" ... and {} more", conversations.len() - 5); } Ok(()) } /// Extract text from various Claude content formats fn extract_text_from_content(content: &Value) -> String { match content { Value::String(s) => s.clone(), Value::Array(arr) => arr .iter() .filter_map(|item| { item.get("text") .and_then(|t| t.as_str()) .map(|text| text.to_string()) }) .collect::<Vec<_>>() .join("\n"), Value::Object(obj) => { if let Some(text) = obj.get("text").and_then(|t| t.as_str()) { text.to_string() } else { String::new() } } _ => String::new(), } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server