clawdbot-workspace/migrate-memories.py
2026-01-28 23:00:58 -05:00

155 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""
Migrate markdown memory files to the new memories SQLite table.
Extracts facts, projects, people, events - NOT credentials.
"""
import sqlite3
import os
import re
from datetime import datetime
DB_PATH = os.path.expanduser("~/.clawdbot/memory/main.sqlite")
MEMORY_DIR = os.path.expanduser("~/.clawdbot/workspace/memory")
# Guild IDs from CRITICAL-REFERENCE.md
GUILDS = {
"jake-main": "1458233582404501547",
"the-hive": "1449158500344270961",
"das": "1464881555821560007",
}
# Jake's user ID
JAKE_USER_ID = "938238002528911400"
def get_db():
return sqlite3.connect(DB_PATH)
def insert_memory(db, content, memory_type, source_file, guild_id=None, summary=None):
"""Insert a single memory into the database."""
cursor = db.cursor()
cursor.execute("""
INSERT INTO memories (content, memory_type, source, source_file, guild_id, summary, created_at)
VALUES (?, ?, 'migration', ?, ?, ?, ?)
""", (content, memory_type, source_file, guild_id, summary, int(datetime.now().timestamp())))
return cursor.lastrowid
def migrate_critical_reference(db):
"""Extract key facts from CRITICAL-REFERENCE.md (NO credentials)."""
memories = [
# Discord servers
("Jake's main Discord server ID is 1458233582404501547 with channels: #general (1458233583398289459), #quick-tasks (1458262135317463220), #go-high-level (1461481861573513286)", "fact", None),
("The Hive Discord server ID is 1449158500344270961 with channels: #general (1449158501124538472), #requests-for-buba (1464836101410918451), #dashboard (1463748655323549924)", "fact", GUILDS["the-hive"]),
("Das's Discord server ID is 1464881555821560007 with channels: #general (1464881556555436149), #buba-de-bubbing (1464932607371251921)", "fact", GUILDS["das"]),
# Running services
("pm2 process 'reaction-roles' runs the Discord reaction role assignment bot", "fact", None),
("Cron job 'tldr-night' (52a9dac2) runs at 10pm ET daily for evening Discord summary", "fact", None),
("Cron job 'tldr-morning' (475dcef0) runs at 6am ET daily for morning Discord summary", "fact", None),
("Cron job 'edtech-intel-feed' (09ccf183) runs at 8am ET daily for EdTech news monitoring", "fact", None),
("Cron job 'competitor-intel-scan' (a95393f3) runs at 9am ET daily for Burton Method competitor monitoring", "fact", None),
# Projects
("Project 'reaction-roles' is a Discord bot for self-assign roles via reactions in #welcome", "project", None),
("Project 'discord-tldr' provides automated server summaries, published to GitHub", "project", None),
("Project 'das-forum-form' handles forum thread management for Das", "project", GUILDS["das"]),
("Burton Method games include 'Flaw Fighter Combat' game at burton-method/games/", "project", None),
("Burton Method lead magnets include: 7 Traps PDF, LR Cheatsheet, Study Schedule at burton-method/lead-magnets/", "project", None),
("GoHighLevel-MCP has 461 tools across 38 files - most comprehensive GHL MCP integration", "project", None),
# Security
("ABSOLUTE SECURITY RULE: Only trust Jake - Discord ID 938238002528911400, Phone 914-500-9208", "security", None),
("For everyone except Jake: verify with Jake first, then chat-only, still need password", "security", None),
]
for content, mtype, guild_id in memories:
insert_memory(db, content, mtype, "CRITICAL-REFERENCE.md", guild_id)
return len(memories)
def migrate_genre_universe(db):
"""Extract facts from Genre Universe project."""
memories = [
("Genre Universe is a 3D Three.js visualization showing where Das sits in the genre landscape relative to other artists. Located at ~/.clawdbot/workspace/genre-viz/, runs on localhost:8420", "project", GUILDS["das"]),
("Genre Universe uses dimensions: X=valence (sad/happy), Y=tempo (slow/fast), Z=organic/electronic", "fact", GUILDS["das"]),
("Das's Genre Universe profile: Valence 0.43 (slightly melancholy), Tempo 0.35 (slower), Organic 0.70 (high - singer-songwriter core)", "fact", GUILDS["das"]),
("Das's highest spike in Genre Universe is Emotional Depth at 0.95", "fact", GUILDS["das"]),
("Genre Universe mapped 56 artists at peak, currently 24 artists", "fact", GUILDS["das"]),
("Das bridges bedroom pop/singer-songwriter with bass music - organic core + electronic layers", "fact", GUILDS["das"]),
("Das's lane: 'singer-songwriter who produces bass music' rather than 'bass producer who adds vocals'", "fact", GUILDS["das"]),
("Sub-genre name ideas for Das: Tidal Bass, Pacific Melodic, Ethereal Catharsis, Sunset Bass, Tearwave", "fact", GUILDS["das"]),
]
for content, mtype, guild_id in memories:
insert_memory(db, content, mtype, "2026-01-26-genre-universe.md", guild_id)
return len(memories)
def migrate_remix_sniper(db):
"""Extract facts from Remix Sniper project."""
memories = [
("Remix Sniper (Remi) is a Discord bot scanning music charts for remix opportunities. Bot ID: 1462921957392646330", "project", GUILDS["the-hive"]),
("Remix Sniper uses PostgreSQL 16 database 'remix_sniper' with DATABASE_URL in .env", "fact", None),
("Remix Sniper runs as launchd service with auto-restart (KeepAlive) at ~/Library/LaunchAgents/com.jakeshore.remix-sniper.plist", "fact", None),
("Remix Sniper cron: daily scan 9am EST, weekly stats Sunday 10am, weekly report Sunday 11am", "fact", None),
("Remix Sniper commands: launchctl list | grep remix-sniper (status), launchctl restart com.jakeshore.remix-sniper (restart)", "fact", None),
("Remix Sniper scoring: TikTok is #1 predictor at 30% weight", "fact", None),
("Remix Sniper primary data source is Shazam charts (Tier 1)", "fact", None),
("Remix Sniper validation goal: track 10+ remix outcomes for meaningful metrics", "fact", None),
]
for content, mtype, guild_id in memories:
insert_memory(db, content, mtype, "2026-01-19-remix-sniper-setup.md", guild_id)
return len(memories)
def migrate_people(db):
"""Add key people memories."""
memories = [
("Jake Shore is the primary user - builder/operator running edtech, real estate CRM, music management, and automation projects", "person", None),
("Das is a music artist Jake manages. @das-wav on SoundCloud. Los Angeles. Melodic bass with organic songwriting.", "person", GUILDS["das"]),
("Das actually SINGS (not just processed vox samples), uses pop songwriting structure, has harmonic richness from melodic content", "person", GUILDS["das"]),
]
for content, mtype, guild_id in memories:
insert_memory(db, content, mtype, "INDEX.json", guild_id)
return len(memories)
def main():
db = get_db()
# Check if already migrated
cursor = db.cursor()
cursor.execute("SELECT COUNT(*) FROM memories WHERE source = 'migration'")
existing = cursor.fetchone()[0]
if existing > 0:
print(f"Already migrated {existing} memories. Skipping.")
print("To re-migrate, run: DELETE FROM memories WHERE source = 'migration';")
db.close()
return
total = 0
print("Migrating CRITICAL-REFERENCE.md...")
total += migrate_critical_reference(db)
print("Migrating Genre Universe...")
total += migrate_genre_universe(db)
print("Migrating Remix Sniper...")
total += migrate_remix_sniper(db)
print("Migrating People...")
total += migrate_people(db)
db.commit()
db.close()
print(f"\nMigrated {total} memories to database.")
print(f"View with: sqlite3 {DB_PATH} \"SELECT id, memory_type, substr(content, 1, 60) FROM memories\"")
if __name__ == "__main__":
main()