Files
claudetools/scripts/import-claude-context.py
Mike Swanson 390b10b32c Complete Phase 6: MSP Work Tracking with Context Recall System
Implements production-ready MSP platform with cross-machine persistent memory for Claude.

API Implementation:
- 130 REST API endpoints across 21 entities
- JWT authentication on all endpoints
- AES-256-GCM encryption for credentials
- Automatic audit logging
- Complete OpenAPI documentation

Database:
- 43 tables in MariaDB (172.16.3.20:3306)
- 42 SQLAlchemy models with modern 2.0 syntax
- Full Alembic migration system
- 99.1% CRUD test pass rate

Context Recall System (Phase 6):
- Cross-machine persistent memory via database
- Automatic context injection via Claude Code hooks
- Automatic context saving after task completion
- 90-95% token reduction with compression utilities
- Relevance scoring with time decay
- Tag-based semantic search
- One-command setup script

Security Features:
- JWT tokens with Argon2 password hashing
- AES-256-GCM encryption for all sensitive data
- Comprehensive audit trail for credentials
- HMAC tamper detection
- Secure configuration management

Test Results:
- Phase 3: 38/38 CRUD tests passing (100%)
- Phase 4: 34/35 core API tests passing (97.1%)
- Phase 5: 62/62 extended API tests passing (100%)
- Phase 6: 10/10 compression tests passing (100%)
- Overall: 144/145 tests passing (99.3%)

Documentation:
- Comprehensive architecture guides
- Setup automation scripts
- API documentation at /api/docs
- Complete test reports
- Troubleshooting guides

Project Status: 95% Complete (Production-Ready)
Phase 7 (optional work context APIs) remains for future enhancement.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 06:00:26 -07:00

285 lines
8.2 KiB
Python

#!/usr/bin/env python3
"""
Claude Context Import Script
Command-line tool to bulk import conversation contexts from Claude project folders.
Usage:
python scripts/import-claude-context.py --folder "C:/Users/MikeSwanson/claude-projects" --dry-run
python scripts/import-claude-context.py --folder "C:/Users/MikeSwanson/claude-projects" --execute
"""
import argparse
import json
import os
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv
def load_jwt_token() -> str:
"""
Load JWT token from .claude/context-recall-config.env
Returns:
JWT token string
Raises:
SystemExit: If token cannot be loaded
"""
# Try multiple possible locations
possible_paths = [
Path(".claude/context-recall-config.env"),
Path("D:/ClaudeTools/.claude/context-recall-config.env"),
Path(__file__).parent.parent / ".claude" / "context-recall-config.env",
]
for env_path in possible_paths:
if env_path.exists():
load_dotenv(env_path)
token = os.getenv("JWT_TOKEN")
if token:
print(f"[OK] Loaded JWT token from {env_path}")
return token
print("[ERROR] Could not find JWT_TOKEN in .claude/context-recall-config.env")
print("\nTried locations:")
for path in possible_paths:
print(f" - {path} ({'exists' if path.exists() else 'not found'})")
print("\nPlease create .claude/context-recall-config.env with:")
print(" JWT_TOKEN=your_token_here")
sys.exit(1)
def get_api_base_url() -> str:
"""
Get API base URL from environment or use default.
Returns:
API base URL string
"""
return os.getenv("API_BASE_URL", "http://localhost:8000")
def call_bulk_import_api(
folder_path: str,
jwt_token: str,
dry_run: bool = True,
project_id: str = None,
session_id: str = None,
) -> dict:
"""
Call the bulk import API endpoint.
Args:
folder_path: Path to folder containing Claude conversations
jwt_token: JWT authentication token
dry_run: Preview mode without saving
project_id: Optional project ID to associate contexts with
session_id: Optional session ID to associate contexts with
Returns:
API response dictionary
Raises:
requests.exceptions.RequestException: If API call fails
"""
api_url = f"{get_api_base_url()}/api/bulk-import/import-folder"
headers = {
"Authorization": f"Bearer {jwt_token}",
"Content-Type": "application/json",
}
params = {
"folder_path": folder_path,
"dry_run": dry_run,
}
if project_id:
params["project_id"] = project_id
if session_id:
params["session_id"] = session_id
print(f"\n[API] Calling: {api_url}")
print(f" Mode: {'DRY RUN' if dry_run else 'EXECUTE'}")
print(f" Folder: {folder_path}")
response = requests.post(api_url, headers=headers, params=params, timeout=300)
response.raise_for_status()
return response.json()
def display_progress(result: dict):
"""
Display import progress and results.
Args:
result: API response dictionary
"""
print("\n" + "=" * 70)
print("IMPORT RESULTS")
print("=" * 70)
# Summary
print(f"\n{result.get('summary', 'No summary available')}")
# Statistics
print(f"\n[STATS]")
print(f" Files scanned: {result.get('files_scanned', 0)}")
print(f" Files processed: {result.get('files_processed', 0)}")
print(f" Contexts created: {result.get('contexts_created', 0)}")
print(f" Errors: {len(result.get('errors', []))}")
# Context preview
contexts_preview = result.get("contexts_preview", [])
if contexts_preview:
print(f"\n[PREVIEW] Contexts (showing {min(5, len(contexts_preview))} of {len(contexts_preview)}):")
for i, ctx in enumerate(contexts_preview[:5], 1):
print(f"\n {i}. {ctx.get('title', 'Untitled')}")
print(f" Type: {ctx.get('type', 'unknown')}")
print(f" Messages: {ctx.get('message_count', 0)}")
print(f" Tags: {', '.join(ctx.get('tags', []))}")
print(f" Relevance: {ctx.get('relevance_score', 0.0):.1f}/10.0")
# Errors
errors = result.get("errors", [])
if errors:
print(f"\n[WARNING] Errors ({len(errors)}):")
for i, error in enumerate(errors[:5], 1):
print(f"\n {i}. File: {error.get('file', 'unknown')}")
print(f" Error: {error.get('error', 'unknown error')}")
if len(errors) > 5:
print(f"\n ... and {len(errors) - 5} more errors")
print("\n" + "=" * 70)
def main():
"""Main entry point for the import script."""
parser = argparse.ArgumentParser(
description="Import Claude conversation contexts from project folders",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Preview import without saving
python scripts/import-claude-context.py --folder "C:\\Users\\MikeSwanson\\claude-projects" --dry-run
# Execute import and save to database
python scripts/import-claude-context.py --folder "C:\\Users\\MikeSwanson\\claude-projects" --execute
# Associate with a specific project
python scripts/import-claude-context.py --folder "C:\\Users\\MikeSwanson\\claude-projects" --execute --project-id abc-123
"""
)
parser.add_argument(
"--folder",
required=True,
help="Path to Claude projects folder containing .jsonl conversation files"
)
mode_group = parser.add_mutually_exclusive_group(required=True)
mode_group.add_argument(
"--dry-run",
action="store_true",
help="Preview import without saving to database"
)
mode_group.add_argument(
"--execute",
action="store_true",
help="Execute import and save to database"
)
parser.add_argument(
"--project-id",
help="Associate all imported contexts with this project ID"
)
parser.add_argument(
"--session-id",
help="Associate all imported contexts with this session ID"
)
parser.add_argument(
"--api-url",
help="API base URL (default: http://localhost:8000)"
)
args = parser.parse_args()
# Set API URL if provided
if args.api_url:
os.environ["API_BASE_URL"] = args.api_url
# Validate folder path
folder_path = Path(args.folder)
if not folder_path.exists():
print(f"[ERROR] Folder does not exist: {folder_path}")
sys.exit(1)
print("=" * 70)
print("CLAUDE CONTEXT IMPORT TOOL")
print("=" * 70)
# Load JWT token
try:
jwt_token = load_jwt_token()
except Exception as e:
print(f"[ERROR] Error loading JWT token: {e}")
sys.exit(1)
# Determine mode
dry_run = args.dry_run
# Call API
try:
result = call_bulk_import_api(
folder_path=str(folder_path),
jwt_token=jwt_token,
dry_run=dry_run,
project_id=args.project_id,
session_id=args.session_id,
)
# Display results
display_progress(result)
# Success message
if dry_run:
print("\n[SUCCESS] Dry run completed successfully!")
print(" Run with --execute to save contexts to database")
else:
print(f"\n[SUCCESS] Import completed successfully!")
print(f" Created {result.get('contexts_created', 0)} contexts")
sys.exit(0)
except requests.exceptions.HTTPError as e:
print(f"\n[ERROR] API Error: {e}")
if e.response is not None:
try:
error_detail = e.response.json()
print(f" Detail: {error_detail.get('detail', 'No details available')}")
except:
print(f" Response: {e.response.text}")
sys.exit(1)
except requests.exceptions.RequestException as e:
print(f"\n[ERROR] Network Error: {e}")
print(" Make sure the API server is running")
sys.exit(1)
except Exception as e:
print(f"\n[ERROR] Unexpected Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()