Newer
Older
cortex-hub / scripts / migrate_skills_to_fs.py
import os
import sys
import json
import logging
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Add the ai-hub to sys.path
sys.path.insert(0, '/app')
from app.db.models import Skill, SkillFile
from app.config import settings

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# The new unified skills directory
BASE_DATA_DIR = "/app/data"
SKILLS_DIR = os.path.join(BASE_DATA_DIR, "skills")
logger.info(f"Target Skills Directory: {SKILLS_DIR}")

def migrate():
    # Setup Database Connection
    db_url = settings.DATABASE_URL
    if not db_url:
        logger.error("DATABASE_URL is not set!")
        sys.exit(1)
        
    engine = create_engine(db_url)
    Session = sessionmaker(bind=engine)
    session = Session()

    try:
        skills = session.query(Skill).all()
        logger.info(f"Found {len(skills)} skills in the database. Starting migration...")

        os.makedirs(SKILLS_DIR, exist_ok=True)

        for skill in skills:
            # 1. Determine Feature Parent Directory (default to 'swarm_control' if none)
            feature = "swarm_control"
            if skill.features and isinstance(skill.features, list) and len(skill.features) > 0:
                feature = skill.features[0]
            elif isinstance(skill.features, str):
                try:
                    features_list = json.loads(skill.features)
                    if features_list: feature = features_list[0]
                except:
                    feature = skill.features

            # Slugify the skill name for the folder
            slug = skill.name.replace(" ", "_").lower()
            
            # Target path: /app/data/skills/swarm_control/get_weather/
            target_path = os.path.join(SKILLS_DIR, feature, slug)
            os.makedirs(target_path, exist_ok=True)
            
            logger.info(f"Migrating Skill '{skill.name}' -> {target_path}")

            # 2. Write Virtual Files
            skill_md_content = None
            has_files = False
            for sf in skill.files:
                has_files = True
                file_abs_path = os.path.join(target_path, sf.file_path)
                os.makedirs(os.path.dirname(file_abs_path), exist_ok=True)
                with open(file_abs_path, "w") as f:
                    f.write(sf.content or "")
                
                if sf.file_path == "SKILL.md":
                    skill_md_content = sf.content

            # If it didn't have a SKILL.md for some reason but has an old-style description:
            if not skill_md_content:
                logger.warning(f"Skill '{skill.name}' had no SKILL.md. Generating one...")
                with open(os.path.join(target_path, "SKILL.md"), "w") as f:
                    f.write(f"### Skill Name: {skill.name}\n\n{skill.description or 'No description provided.'}\n")

            # 3. Write Invisible Metadata File (.metadata.json)
            # This handles access control and legacy UI metadata properties
            metadata = {
                "owner_id": skill.owner_id,
                "is_system": skill.is_system,
                "id": skill.id,  # keep track just in case
                "extra_metadata": skill.extra_metadata or {"emoji": "🛠️"}
            }
            with open(os.path.join(target_path, ".metadata.json"), "w") as f:
                json.dump(metadata, f, indent=4)
                
        logger.info("Migration strictly to File System completed perfectly!")

    except Exception as e:
        logger.error(f"Migration Failed: {e}")
    finally:
        session.close()

if __name__ == "__main__":
    migrate()