diff options
| author | Dhravya Shah <[email protected]> | 2025-10-01 18:11:37 -0700 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2025-10-01 18:11:37 -0700 |
| commit | 5c575785737b06beafa09943ecff13be6027d2b7 (patch) | |
| tree | 887d95ae179814a93fbba6df0f81115f3fc53f61 | |
| parent | feat: Claude memory integration (diff) | |
| download | supermemory-5c575785737b06beafa09943ecff13be6027d2b7.tar.xz supermemory-5c575785737b06beafa09943ecff13be6027d2b7.zip | |
feat: Add memory vs rag and migration section to docs
| -rw-r--r-- | apps/docs/docs.json | 15 | ||||
| -rw-r--r-- | apps/docs/memory-vs-rag.mdx | 239 | ||||
| -rw-r--r-- | apps/docs/migration/from-mem0.mdx | 303 | ||||
| -rw-r--r-- | apps/docs/migration/mem0-migration-script.py | 337 | ||||
| -rw-r--r-- | packages/tools/src/claude-memory.ts | 143 |
5 files changed, 986 insertions, 51 deletions
diff --git a/apps/docs/docs.json b/apps/docs/docs.json index e658df17..217e74fb 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -71,7 +71,11 @@ "pages": [ { "group": "Getting Started", - "pages": ["intro", "routervsapi", "quickstart"] + "pages": ["intro", "routervsapi", "quickstart", "memory-vs-rag"] + }, + { + "group": "Migration Guides", + "pages": ["migration/from-mem0"] }, { "group": "Memory API", @@ -83,7 +87,7 @@ "pages": [ "add-memories/overview", "add-memories/parameters", - "memory-api/ingesting", + "api/ingesting", { "group": "Examples", "pages": [ @@ -112,7 +116,7 @@ ] }, "search/filtering", - "memory-api/track-progress", + "api/track-progress", { "group": "List Memories", "icon": "list", @@ -185,11 +189,6 @@ "pages": ["memory-api/sdks/openai-plugins", "ai-sdk/npm"] }, { - "group": "Anthropic SDK", - "icon": "sparkles", - "pages": ["memory-api/sdks/anthropic-claude-memory"] - }, - { "group": "AI SDK", "icon": "triangle", "pages": [ diff --git a/apps/docs/memory-vs-rag.mdx b/apps/docs/memory-vs-rag.mdx new file mode 100644 index 00000000..699b906d --- /dev/null +++ b/apps/docs/memory-vs-rag.mdx @@ -0,0 +1,239 @@ +--- +title: "Memory vs RAG: Understanding the Difference" +description: "Learn why agent memory and RAG are fundamentally different, and when to use each approach" +sidebarTitle: "Memory vs RAG" +--- + +Most developers confuse RAG (Retrieval-Augmented Generation) with agent memory. They're not the same thing, and using RAG for memory is why your agents keep forgetting important context. Let's understand the fundamental difference. + +## The Core Problem + +When building AI agents, developers often treat memory as just another retrieval problem. They store conversations in a vector database, embed queries, and hope semantic search will surface the right context. + +**This approach fails because memory isn't about finding similar textβit's about understanding relationships, temporal context, and user state over time.** + +## Documents vs Memories in Supermemory + +Supermemory makes a clear distinction between these two concepts: + +### Documents: Raw Knowledge +Documents are the raw content you send to SupermemoryβPDFs, web pages, text files. They represent static knowledge that doesn't change based on who's accessing it. + +**Characteristics:** +- **Stateless**: A document about Python programming is the same for everyone +- **Unversioned**: Content doesn't track changes over time +- **Universal**: Not linked to specific users or entities +- **Searchable**: Perfect for semantic similarity search + +**Use Cases:** +- Company knowledge bases +- Technical documentation +- Research papers +- General reference material + +### Memories: Contextual Understanding +Memories are the insights, preferences, and relationships extracted from documents and conversations. They're tied to specific users or entities and evolve over time. + +**Characteristics:** +- **Stateful**: "User prefers dark mode" is specific to that user +- **Temporal**: Tracks when facts became true or invalid +- **Personal**: Linked to users, sessions, or entities +- **Relational**: Understands connections between facts + +**Use Cases:** +- User preferences and history +- Conversation context +- Personal facts and relationships +- Behavioral patterns + +## Why RAG Fails as Memory + +Let's look at a real scenario that illustrates the problem: + +<Tabs> + <Tab title="The Scenario"> + ``` + Day 1: "I love Adidas sneakers" + Day 30: "My Adidas broke after a month, terrible quality" + Day 31: "I'm switching to Puma" + Day 45: "What sneakers should I buy?" + ``` + </Tab> + + <Tab title="RAG Approach (Wrong)"> + ```python + # RAG sees these as isolated embeddings + query = "What sneakers should I buy?" + + # Semantic search finds closest match + result = vector_search(query) + # Returns: "I love Adidas sneakers" (highest similarity) + + # Agent recommends Adidas π€¦ + ``` + + **Problem**: RAG finds the most semantically similar text but misses the temporal progression and causal relationships. + </Tab> + + <Tab title="Memory Approach (Right)"> + ```python + # Supermemory understands temporal context + query = "What sneakers should I buy?" + + # Memory retrieval considers: + # 1. Temporal validity (Adidas preference is outdated) + # 2. Causal relationships (broke β disappointment β switch) + # 3. Current state (now prefers Puma) + + # Agent correctly recommends Puma β
+ ``` + + **Solution**: Memory systems track when facts become invalid and understand causal chains. + </Tab> +</Tabs> + +## The Technical Difference + +### RAG: Semantic Similarity +``` +Query β Embedding β Vector Search β Top-K Results β LLM +``` + +RAG excels at finding information that's semantically similar to your query. It's statelessβeach query is independent. + +### Memory: Contextual Graph +``` +Query β Entity Recognition β Graph Traversal β Temporal Filtering β Context Assembly β LLM +``` + +Memory systems build a knowledge graph that understands: +- **Entities**: Users, products, concepts +- **Relationships**: Preferences, ownership, causality +- **Temporal Context**: When facts were true +- **Invalidation**: When facts became outdated + +## When to Use Each + +<CardGroup cols={2}> + <Card title="Use RAG For" icon="search"> + - Static documentation + - Knowledge bases + - Research queries + - General Q&A + - Content that doesn't change per user + </Card> + + <Card title="Use Memory For" icon="brain"> + - User preferences + - Conversation history + - Personal facts + - Behavioral patterns + - Anything that evolves over time + </Card> +</CardGroup> + +## Real-World Examples + +### E-commerce Assistant + +<Tabs> + <Tab title="RAG Component"> + Stores product catalogs, specifications, reviews + + ```python + # Good for RAG + "What are the specs of iPhone 15?" + "Compare Nike and Adidas running shoes" + "Show me waterproof jackets" + ``` + </Tab> + + <Tab title="Memory Component"> + Tracks user preferences, purchase history, interactions + + ```python + # Needs Memory + "What size do I usually wear?" + "Did I like my last purchase?" + "What's my budget preference?" + ``` + </Tab> +</Tabs> + +### Customer Support Bot + +<Tabs> + <Tab title="RAG Component"> + FAQ documents, troubleshooting guides, policies + + ```python + # Good for RAG + "How do I reset my password?" + "What's your return policy?" + "Troubleshooting WiFi issues" + ``` + </Tab> + + <Tab title="Memory Component"> + Previous issues, user account details, conversation context + + ```python + # Needs Memory + "Is my issue from last week resolved?" + "What plan am I on?" + "You were helping me with..." + ``` + </Tab> +</Tabs> + +## How Supermemory Handles Both + +Supermemory provides a unified platform that correctly handles both patterns: + +### 1. Document Storage (RAG) +```python +# Add a document for RAG-style retrieval +client.memories.add( + content="iPhone 15 has a 48MP camera and A17 Pro chip", + # No user association - universal knowledge +) +``` + +### 2. Memory Creation +```python +# Add a user-specific memory +client.memories.add( + content="User prefers Android over iOS", + container_tags=["user_123"], # User-specific + metadata={ + "type": "preference", + "confidence": "high" + } +) +``` + +### 3. Hybrid Retrieval +```python +# Search combines both approaches +results = client.memories.search( + query="What phone should I recommend?", + container_tags=["user_123"], # Gets user memories + # Also searches general knowledge +) + +# Results include: +# - User's Android preference (memory) +# - Latest Android phone specs (documents) +``` + +## The Bottom Line + +<Note> +**Key Insight**: RAG answers "What do I know?" while Memory answers "What do I remember about you?" +</Note> + +Stop treating memory like a retrieval problem. Your agents need both: +- **RAG** for accessing knowledge +- **Memory** for understanding users + +Supermemory provides both capabilities in a unified platform, ensuring your agents have the right context at the right time. diff --git a/apps/docs/migration/from-mem0.mdx b/apps/docs/migration/from-mem0.mdx new file mode 100644 index 00000000..e24a5548 --- /dev/null +++ b/apps/docs/migration/from-mem0.mdx @@ -0,0 +1,303 @@ +--- +title: "Migrating from Mem0.ai to Supermemory" +description: "Complete guide to migrate your data and applications from Mem0.ai to Supermemory" +sidebarTitle: "From Mem0" +--- + +Migrating from Mem0.ai to Supermemory is straightforward. This guide walks you through exporting your memories from Mem0 and importing them into Supermemory. + +## Why Migrate to Supermemory? + +Supermemory offers enhanced capabilities over Mem0.ai: +- **Memory Router** for zero-code LLM integration +- **Knowledge graph** architecture for better context relationships +- **Multiple content types** (URLs, PDFs, images, videos) +- **Generous free tier** (100k tokens) with affordable pricing +- **Multiple integration options** (API, Router, MCP, SDKs) + +## Quick Migration (All-in-One) + +Complete migration in one script: + +```python +from mem0 import MemoryClient +from supermemory import Supermemory +import json, time + +# Export from Mem0 +mem0 = MemoryClient(api_key="your_mem0_api_key") +export = mem0.create_memory_export( + schema={"type": "object", "properties": {"memories": {"type": "array", "items": {"type": "object"}}}}, + filters={} +) +time.sleep(5) +data = mem0.get_memory_export(memory_export_id=export["id"]) + +# Import to Supermemory +supermemory = Supermemory(api_key="your_supermemory_api_key") +for memory in data["memories"]: + if memory.get("content"): + supermemory.memories.add( + content=memory["content"], + container_tags=["imported_from_mem0"] + ) + print(f"β
{memory['content'][:50]}...") + +print("Migration complete!") +``` + +## Step-by-Step Migration + +<Steps> + <Step title="Export from Mem0.ai"> + Mem0 provides two ways to export your memories: + + ### Option 1: Export via Dashboard (Recommended) + 1. Log into your [Mem0 dashboard](https://app.mem0.ai) + 2. Navigate to the export section + 3. Download your memories as JSON + + ### Option 2: Export via API + + Simple script to export all your memories from Mem0: + + ```python + from mem0 import MemoryClient + import json + import time + + # Connect to Mem0 + client = MemoryClient(api_key="your_mem0_api_key") + + # Create export job + schema = { + "type": "object", + "properties": { + "memories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "content": {"type": "string"}, + "metadata": {"type": "object"}, + "created_at": {"type": "string"} + } + } + } + } + } + + response = client.create_memory_export(schema=schema, filters={}) + export_id = response["id"] + + # Wait and retrieve + print("Exporting memories...") + time.sleep(5) + export_data = client.get_memory_export(memory_export_id=export_id) + + # Save to file + with open("mem0_export.json", "w") as f: + json.dump(export_data, f, indent=2) + + print(f"Exported {len(export_data['memories'])} memories") + ``` + </Step> + + <Step title="Set Up Supermemory"> + Create your Supermemory account and get your API key: + + 1. Sign up at [console.supermemory.ai](https://console.supermemory.ai) + 2. Create a new project + 3. Generate an API key from the dashboard + + ```bash + # Set your environment variable + export SUPERMEMORY_API_KEY="your_supermemory_api_key" + ``` + </Step> + + <Step title="Import to Supermemory"> + Simple script to import your Mem0 memories into Supermemory: + + ```python + import json + from supermemory import Supermemory + + # Load your Mem0 export + with open("mem0_export.json", "r") as f: + mem0_data = json.load(f) + + # Connect to Supermemory + client = Supermemory(api_key="your_supermemory_api_key") + + # Import memories + for memory in mem0_data["memories"]: + content = memory.get("content", "") + + # Skip empty memories + if not content: + continue + + # Import to Supermemory + try: + result = client.memories.add( + content=content, + container_tags=["imported_from_mem0"], + metadata={ + "source": "mem0", + "created_at": memory.get("created_at"), + **(memory.get("metadata") or {}) + } + ) + print(f"Imported: {content[:50]}...") + except Exception as e: + print(f"Failed: {e}") + + print("Migration complete!") + ``` + </Step> +</Steps> + +## API Migration Reference + +Here's how common Mem0.ai operations map to Supermemory: + +### Adding Memories + +<CodeGroup> + +```python Mem0.ai +from mem0 import MemoryClient + +client = MemoryClient(api_key="...") +client.add( + messages="User prefers dark mode", + user_id="alice" +) +``` + +```python Supermemory +from supermemory import Supermemory + +client = Supermemory(api_key="...") +client.memories.add( + content="User prefers dark mode", + container_tags=["user_alice"] +) +``` + +</CodeGroup> + +### Searching Memories + +<CodeGroup> + +```python Mem0.ai +results = client.search( + query="user preferences", + user_id="alice" +) +``` + +```python Supermemory +results = client.memories.search( + query="user preferences", + container_tags=["user_alice"] +) +``` + +</CodeGroup> + +### Getting All Memories + +<CodeGroup> + +```python Mem0.ai +memories = client.get_all( + user_id="alice" +) +``` + +```python Supermemory +memories = client.memories.list( + container_tags=["user_alice"], + limit=100 +) +``` + +</CodeGroup> + +### Deleting Memories + +<CodeGroup> + +```python Mem0.ai +client.delete(memory_id="mem_123") +``` + +```python Supermemory +client.memories.delete("mem_123") +``` + +</CodeGroup> + +## Using Memory Router (Easiest Migration) + +For the simplest migration path, use Supermemory's Memory Router which requires minimal code changes: + +<CodeGroup> + +```python Before (Mem0 + OpenAI) +from openai import OpenAI +from mem0 import MemoryClient + +# Two separate clients needed +openai = OpenAI(api_key="sk-...") +memory = MemoryClient(api_key="mem0_key") + +# Manual memory management +context = memory.search("user preferences", user_id="alice") +messages = [ + {"role": "system", "content": f"Context: {context}"}, + {"role": "user", "content": "What are my preferences?"} +] + +response = openai.chat.completions.create( + model="gpt-4", + messages=messages +) +``` + +```python After (Supermemory Router) +from openai import OpenAI + +# Single client with automatic memory management +client = OpenAI( + api_key="sk-...", + base_url="https://api.supermemory.ai/v3/https://api.openai.com/v1", + default_headers={ + "x-supermemory-api-key": "your_supermemory_key", + "x-supermemory-user-id": "alice" + } +) + +# Memories handled automatically! +response = client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "What are my preferences?"}] +) +``` + +</CodeGroup> + +<Note> +For enterprise migrations, [contact us](mailto:[email protected]) for assistance. +</Note> + +## Next Steps + +1. [Explore](/how-it-works) how Supermemory works +2. Read the [quickstart](/quickstart) and add and retrieve your first memories +3. [Connect](/connectors/overview) to Google Drive, Notion, and OneDrive with automatic syncing + diff --git a/apps/docs/migration/mem0-migration-script.py b/apps/docs/migration/mem0-migration-script.py new file mode 100644 index 00000000..7b05edf6 --- /dev/null +++ b/apps/docs/migration/mem0-migration-script.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +""" +Mem0.ai to Supermemory Migration Script +======================================== +Simple script to migrate memories from Mem0.ai to Supermemory. + +Prerequisites: +1. Install required packages: + pip install mem0ai supermemory python-dotenv + +2. Set environment variables: + export MEM0_API_KEY="your_mem0_api_key" + export MEM0_ORG_ID="your_org_id" # Optional + export MEM0_PROJECT_ID="your_project_id" # Optional + export SUPERMEMORY_API_KEY="your_supermemory_api_key" + +Usage: + python mem0-migration-script.py +""" + +import os +import json +import time +from datetime import datetime +from typing import Dict, Any, Optional +from mem0 import MemoryClient +from supermemory import Supermemory +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +def export_from_mem0( + api_key: str, + org_id: Optional[str] = None, + project_id: Optional[str] = None, + filters: Optional[Dict] = None +) -> Dict[str, Any]: + """ + Export memories from Mem0.ai using their export API + """ + print("π Starting Mem0.ai export...") + + # Initialize Mem0 client + client = MemoryClient( + api_key=api_key, + org_id=org_id, + project_id=project_id + ) + + # Define export schema - this matches what Mem0 actually returns + export_schema = { + "type": "object", + "properties": { + "memories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "content": {"type": "string"}, + "user_id": {"type": "string"}, + "agent_id": {"type": "string"}, + "app_id": {"type": "string"}, + "run_id": {"type": "string"}, + "metadata": {"type": "object"}, + "created_at": {"type": "string"}, + "updated_at": {"type": "string"} + } + } + } + } + } + + try: + # Step 1: Create export job + print("π€ Creating export job...") + export_response = client.create_memory_export( + schema=export_schema, + filters=filters if filters else {} + ) + + export_id = export_response.get("id") + print(f"β
Export job created with ID: {export_id}") + + # Step 2: Wait for export to complete + print("β³ Waiting for export to complete...") + time.sleep(5) # Usually takes a few seconds + + # Step 3: Retrieve the exported data using the correct method + print("π₯ Retrieving exported data...") + export_data = client.get_memory_export(memory_export_id=export_id) + + # Step 4: Save backup + backup_filename = f"mem0_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + with open(backup_filename, "w") as f: + json.dump(export_data, f, indent=2) + print(f"πΎ Backup saved to: {backup_filename}") + + memory_count = len(export_data.get("memories", [])) + print(f"β
Successfully exported {memory_count} memories from Mem0.ai") + + # Show sample of exported data + if memory_count > 0: + print("\nπ Sample exported memory:") + sample = export_data["memories"][0] + print(f" Content: {sample.get('content', 'N/A')[:50]}...") + print(f" ID: {sample.get('id', 'None')}") + print(f" User ID: {sample.get('user_id', 'None')}") + + return export_data + + except Exception as e: + print(f"β Error exporting from Mem0: {str(e)}") + raise + +def import_to_supermemory(mem0_data: Dict[str, Any], api_key: str) -> Dict[str, int]: + """ + Import Mem0 memories into Supermemory + """ + print("\nπ Starting import to Supermemory...") + + # Initialize Supermemory client + client = Supermemory(api_key=api_key) + + memories = mem0_data.get("memories", []) + if not memories: + print("β οΈ No memories found to import") + return {"imported": 0, "failed": 0, "skipped": 0} + + # Statistics + stats = { + "imported": 0, + "failed": 0, + "skipped": 0 + } + + print(f"π¦ Processing {len(memories)} memories...") + + for i, memory in enumerate(memories, 1): + try: + # Check if content exists + content = memory.get("content", "").strip() + if not content: + print(f"β οΈ [{i}/{len(memories)}] Skipping: No content") + stats["skipped"] += 1 + continue + + # Build container tags + container_tags = ["imported_from_mem0"] + + # Add user tag if present (handle None values) + user_id = memory.get("user_id") + if user_id and user_id != "None": + container_tags.append(f"user_{user_id}") + + # Add agent tag if present + agent_id = memory.get("agent_id") + if agent_id and agent_id != "None": + container_tags.append(f"agent_{agent_id}") + + # Add app tag if present + app_id = memory.get("app_id") + if app_id and app_id != "None": + container_tags.append(f"app_{app_id}") + + # Add session tag if present + session_id = memory.get("session_id") + if session_id and session_id != "None": + container_tags.append(f"session_{session_id}") + + # Generate a unique ID if Mem0 didn't provide one + memory_id = memory.get("id") + if not memory_id or memory_id == "None": + # Use content hash for uniqueness + import hashlib + memory_id = hashlib.md5(content.encode()).hexdigest()[:8] + + # Prepare metadata + metadata = { + "source": "mem0_migration", + "migration_date": datetime.now().isoformat() + } + + # Add original ID if it existed + if memory.get("id") and memory["id"] != "None": + metadata["original_id"] = memory["id"] + + # Add timestamps if available and not None + created_at = memory.get("created_at") + if created_at and created_at != "None": + metadata["original_created_at"] = created_at + + updated_at = memory.get("updated_at") + if updated_at and updated_at != "None": + metadata["original_updated_at"] = updated_at + + # Add hash information if available + hash_val = memory.get("hash") + if hash_val and hash_val != "None": + metadata["original_hash"] = hash_val + + prev_hash = memory.get("prev_hash") + if prev_hash and prev_hash != "None": + metadata["original_prev_hash"] = prev_hash + + # Merge with existing metadata if it's a valid dict + if memory.get("metadata") and isinstance(memory["metadata"], dict): + metadata.update(memory["metadata"]) + + # Import to Supermemory + result = client.memories.add( + content=content, + container_tags=container_tags, + custom_id=f"mem0_{memory_id}", + metadata=metadata + ) + + stats["imported"] += 1 + print(f"β
[{i}/{len(memories)}] Imported: {content[:50]}...") + + # Small delay to avoid rate limiting + if i % 10 == 0: + time.sleep(0.5) + + except Exception as e: + stats["failed"] += 1 + print(f"β [{i}/{len(memories)}] Failed: {str(e)}") + + return stats + +def verify_migration(api_key: str, expected_count: int): + """ + Verify that memories were imported correctly + """ + print("\nπ Verifying migration...") + + client = Supermemory(api_key=api_key) + + try: + # Check imported memories + result = client.memories.list( + container_tags=["imported_from_mem0"], + limit=100 + ) + + total_imported = result['pagination']['totalItems'] + print(f"β
Found {total_imported} imported memories in Supermemory") + + # Show sample memories + if result['memories']: + print("\nπ Sample imported memories:") + for memory in result['memories'][:3]: + print(f" - {memory['id']}: {memory.get('summary', 'No summary')[:50]}...") + + # Check success rate + success_rate = (total_imported / expected_count * 100) if expected_count > 0 else 0 + print(f"\nπ Migration success rate: {success_rate:.1f}%") + + return total_imported + + except Exception as e: + print(f"β Error during verification: {str(e)}") + return 0 + +def main(): + """Main migration function""" + print("=" * 60) + print("π― Mem0.ai to Supermemory Migration Tool") + print("=" * 60) + + # Get credentials from environment + mem0_api_key = os.getenv("MEM0_API_KEY") + mem0_org_id = os.getenv("MEM0_ORG_ID") + mem0_project_id = os.getenv("MEM0_PROJECT_ID") + supermemory_api_key = os.getenv("SUPERMEMORY_API_KEY") + + # Validate credentials + if not mem0_api_key: + print("β Error: MEM0_API_KEY environment variable not set") + return + + if not supermemory_api_key: + print("β Error: SUPERMEMORY_API_KEY environment variable not set") + return + + try: + # Step 1: Export from Mem0 + print("\nπ€ STEP 1: Export from Mem0.ai") + print("-" * 40) + + # You can add filters here if needed + # Example: filters = {"AND": [{"user_id": "specific_user"}]} + filters = None + + mem0_data = export_from_mem0( + api_key=mem0_api_key, + org_id=mem0_org_id, + project_id=mem0_project_id, + filters=filters + ) + + # Step 2: Import to Supermemory + print("\nπ₯ STEP 2: Import to Supermemory") + print("-" * 40) + + stats = import_to_supermemory(mem0_data, supermemory_api_key) + + # Step 3: Verify migration + print("\nβοΈ STEP 3: Verification") + print("-" * 40) + + expected_count = len(mem0_data.get("memories", [])) + verify_migration(supermemory_api_key, expected_count) + + # Final summary + print("\n" + "=" * 60) + print("π MIGRATION SUMMARY") + print("=" * 60) + print(f"π€ Exported from Mem0: {expected_count}") + print(f"β
Successfully imported: {stats['imported']}") + print(f"β οΈ Skipped (no content): {stats['skipped']}") + print(f"β Failed: {stats['failed']}") + + if stats['imported'] == expected_count - stats['skipped']: + print("\nπ Migration completed successfully!") + elif stats['imported'] > 0: + print("\nβ οΈ Migration completed with some issues. Check the logs above.") + else: + print("\nβ Migration failed. Please check your credentials and try again.") + + except Exception as e: + print(f"\nβ Migration error: {str(e)}") + print("Please check your credentials and network connection.") + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts index 49ff39a7..54da77a1 100644 --- a/packages/tools/src/claude-memory.ts +++ b/packages/tools/src/claude-memory.ts @@ -1,6 +1,6 @@ import Supermemory from "supermemory" -import type { SupermemoryToolsConfig } from "./types" import { getContainerTags } from "./shared" +import type { SupermemoryToolsConfig } from "./types" // Claude Memory Tool Types export interface ClaudeMemoryConfig extends SupermemoryToolsConfig { @@ -92,24 +92,45 @@ export class ClaudeMemoryTool { return await this.view(command.path, command.view_range) case "create": if (!command.file_text) { - return { success: false, error: "file_text is required for create command" } + return { + success: false, + error: "file_text is required for create command", + } } return await this.create(command.path, command.file_text) case "str_replace": if (!command.old_str || !command.new_str) { - return { success: false, error: "old_str and new_str are required for str_replace command" } + return { + success: false, + error: "old_str and new_str are required for str_replace command", + } } - return await this.strReplace(command.path, command.old_str, command.new_str) + return await this.strReplace( + command.path, + command.old_str, + command.new_str, + ) case "insert": if (command.insert_line === undefined || !command.insert_text) { - return { success: false, error: "insert_line and insert_text are required for insert command" } + return { + success: false, + error: + "insert_line and insert_text are required for insert command", + } } - return await this.insert(command.path, command.insert_line, command.insert_text) + return await this.insert( + command.path, + command.insert_line, + command.insert_text, + ) case "delete": return await this.delete(command.path) case "rename": if (!command.new_path) { - return { success: false, error: "new_path is required for rename command" } + return { + success: false, + error: "new_path is required for rename command", + } } return await this.rename(command.path, command.new_path) default: @@ -129,14 +150,17 @@ export class ClaudeMemoryTool { /** * Handle command and return properly formatted tool result */ - async handleCommandForToolResult(command: MemoryCommand, toolUseId: string): Promise<MemoryToolResult> { + async handleCommandForToolResult( + command: MemoryCommand, + toolUseId: string, + ): Promise<MemoryToolResult> { const response = await this.handleCommand(command) return { type: "tool_result", tool_use_id: toolUseId, content: response.success - ? (response.content || "Operation completed successfully") + ? response.content || "Operation completed successfully" : `Error: ${response.error}`, is_error: !response.success, } @@ -145,7 +169,10 @@ export class ClaudeMemoryTool { /** * View command: List directory contents or read file with optional line range */ - private async view(path: string, viewRange?: [number, number]): Promise<MemoryResponse> { + private async view( + path: string, + viewRange?: [number, number], + ): Promise<MemoryResponse> { // If path ends with / or is exactly /memories, it's a directory listing request if (path.endsWith("/") || path === "/memories") { // Normalize path to end with / @@ -202,10 +229,7 @@ export class ClaudeMemoryTool { } // Format directory listing - const entries = [ - ...Array.from(dirs).sort(), - ...files.sort() - ] + const entries = [...Array.from(dirs).sort(), ...files.sort()] if (entries.length === 0) { return { @@ -216,7 +240,7 @@ export class ClaudeMemoryTool { return { success: true, - content: `Directory: ${dirPath}\n${entries.map(entry => `- ${entry}`).join('\n')}`, + content: `Directory: ${dirPath}\n${entries.map((entry) => `- ${entry}`).join("\n")}`, } } catch (error) { return { @@ -229,7 +253,10 @@ export class ClaudeMemoryTool { /** * Read file contents with optional line range */ - private async readFile(filePath: string, viewRange?: [number, number]): Promise<MemoryResponse> { + private async readFile( + filePath: string, + viewRange?: [number, number], + ): Promise<MemoryResponse> { try { const normalizedId = this.normalizePathToCustomId(filePath) @@ -241,7 +268,9 @@ export class ClaudeMemoryTool { }) // Try to find exact match by customId - const exactMatch = response.results?.find(r => r.customId === normalizedId) + const exactMatch = response.results?.find( + (r) => r.documentId === normalizedId, + ) const document = exactMatch || response.results?.[0] if (!document) { @@ -251,29 +280,31 @@ export class ClaudeMemoryTool { } } - let content = document.raw || document.content || "" + let content = document.content || "" // Apply line range if specified if (viewRange) { - const lines = content.split('\n') + const lines = content.split("\n") const [startLine, endLine] = viewRange const selectedLines = lines.slice(startLine - 1, endLine) // Format with line numbers - const numberedLines = selectedLines.map((line, index) => { - const lineNum = startLine + index - return `${lineNum.toString().padStart(4)}\t${line}` - }) - - content = numberedLines.join('\n') + const numberedLines = selectedLines.map( + (line: string, index: number) => { + const lineNum = startLine + index + return `${lineNum.toString().padStart(4)}\t${line}` + }, + ) + + content = numberedLines.join("\n") } else { // Format all lines with line numbers - const lines = content.split('\n') + const lines = content.split("\n") const numberedLines = lines.map((line, index) => { const lineNum = index + 1 return `${lineNum.toString().padStart(4)}\t${line}` }) - content = numberedLines.join('\n') + content = numberedLines.join("\n") } return { @@ -291,7 +322,10 @@ export class ClaudeMemoryTool { /** * Create command: Create or overwrite a memory file */ - private async create(filePath: string, fileText: string): Promise<MemoryResponse> { + private async create( + filePath: string, + fileText: string, + ): Promise<MemoryResponse> { try { const normalizedId = this.normalizePathToCustomId(filePath) @@ -302,7 +336,7 @@ export class ClaudeMemoryTool { metadata: { claude_memory_type: "file", file_path: filePath, - line_count: fileText.split('\n').length, + line_count: fileText.split("\n").length, created_by: "claude_memory_tool", last_modified: new Date().toISOString(), }, @@ -323,7 +357,11 @@ export class ClaudeMemoryTool { /** * String replace command: Replace text in existing file */ - private async strReplace(filePath: string, oldStr: string, newStr: string): Promise<MemoryResponse> { + private async strReplace( + filePath: string, + oldStr: string, + newStr: string, + ): Promise<MemoryResponse> { try { // First, find and read the existing file const readResult = await this.getFileDocument(filePath) @@ -334,7 +372,8 @@ export class ClaudeMemoryTool { } } - const originalContent = readResult.document.raw || readResult.document.content || "" + const originalContent = + readResult.document.raw || readResult.document.content || "" // Check if old_str exists in the content if (!originalContent.includes(oldStr)) { @@ -355,7 +394,7 @@ export class ClaudeMemoryTool { containerTags: this.containerTags, metadata: { ...readResult.document.metadata, - line_count: newContent.split('\n').length, + line_count: newContent.split("\n").length, last_modified: new Date().toISOString(), }, }) @@ -375,7 +414,11 @@ export class ClaudeMemoryTool { /** * Insert command: Insert text at specific line */ - private async insert(filePath: string, insertLine: number, insertText: string): Promise<MemoryResponse> { + private async insert( + filePath: string, + insertLine: number, + insertText: string, + ): Promise<MemoryResponse> { try { // First, find and read the existing file const readResult = await this.getFileDocument(filePath) @@ -386,8 +429,9 @@ export class ClaudeMemoryTool { } } - const originalContent = readResult.document.raw || readResult.document.content || "" - const lines = originalContent.split('\n') + const originalContent = + readResult.document.raw || readResult.document.content || "" + const lines = originalContent.split("\n") // Validate line number if (insertLine < 1 || insertLine > lines.length + 1) { @@ -399,7 +443,7 @@ export class ClaudeMemoryTool { // Insert the text (insertLine is 1-based) lines.splice(insertLine - 1, 0, insertText) - const newContent = lines.join('\n') + const newContent = lines.join("\n") // Update the document const normalizedId = this.normalizePathToCustomId(filePath) @@ -409,7 +453,7 @@ export class ClaudeMemoryTool { containerTags: this.containerTags, metadata: { ...readResult.document.metadata, - line_count: newContent.split('\n').length, + line_count: newContent.split("\n").length, last_modified: new Date().toISOString(), }, }) @@ -459,7 +503,10 @@ export class ClaudeMemoryTool { /** * Rename command: Move/rename memory file */ - private async rename(oldPath: string, newPath: string): Promise<MemoryResponse> { + private async rename( + oldPath: string, + newPath: string, + ): Promise<MemoryResponse> { try { // Validate new path if (!this.isValidPath(newPath)) { @@ -478,7 +525,8 @@ export class ClaudeMemoryTool { } } - const originalContent = readResult.document.raw || readResult.document.content || "" + const originalContent = + readResult.document.raw || readResult.document.content || "" const newNormalizedId = this.normalizePathToCustomId(newPath) // Create new document with new path @@ -526,7 +574,9 @@ export class ClaudeMemoryTool { }) // Try to find exact match by customId first - const exactMatch = response.results?.find(r => r.customId === normalizedId) + const exactMatch = response.results?.find( + (r) => r.documentId === normalizedId, + ) const document = exactMatch || response.results?.[0] if (!document) { @@ -552,13 +602,20 @@ export class ClaudeMemoryTool { * Validate that path starts with /memories for security */ private isValidPath(path: string): boolean { - return (path.startsWith("/memories/") || path === "/memories") && !path.includes("../") && !path.includes("..\\") + return ( + (path.startsWith("/memories/") || path === "/memories") && + !path.includes("../") && + !path.includes("..\\") + ) } } /** * Create a Claude memory tool instance */ -export function createClaudeMemoryTool(apiKey: string, config?: ClaudeMemoryConfig) { +export function createClaudeMemoryTool( + apiKey: string, + config?: ClaudeMemoryConfig, +) { return new ClaudeMemoryTool(apiKey, config) -}
\ No newline at end of file +} |