Week 11 of 16

Build: Migrate the App to SQLite

Update vault.py and app.py to use the Database class instead of JSON-based PromptLibrary.

Day 54 75 minutes Build

Day 54 of 80

The Migration Plan

You built database.py yesterday. Today you wire it into your actual app. The goal: your CLI and web app both use SQLite instead of JSON, and the behavior stays the same.

The API is similar but not identical:

The big shift: you no longer delete by index. The database identifies rows by ID, not position. This is actually better — deleting index 0 was always fragile.

Update vault.py

vault.py — SQLite version Python
import anthropic
from dotenv import load_dotenv
from database import Database

load_dotenv()
client = anthropic.Anthropic()
db = Database("prompt_vault.db")


def generate_prompt_text(shot, platform):
    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=300,
        system="You are an expert AI video prompt engineer.",
        messages=[{
            "role": "user",
            "content": f"Write a production-ready {platform} AI video prompt for: {shot}. "
                       f"Include camera, lighting, mood. Under 100 words. Return only the prompt."
        }]
    )
    return message.content[0].text


print("=== DVP Prompt Vault ===\n")

while True:
    all_prompts = db.get_all()
    print(f"Library: {len(all_prompts)} prompts")
    print("1. View all  2. Generate  3. Search  4. Filter  5. Delete by ID  6. Stats  7. Quit")
    choice = input("\nChoose: ")

    if choice == "1":
        if not all_prompts:
            print("\n  (empty)\n")
        else:
            for row in all_prompts:
                # row is a sqlite3.Row — access by column name
                print(f"  #{row['id']} [{row['platform']}] {row['shot']}")
                preview = row['prompt'][:60] + "..." if len(row['prompt']) > 60 else row['prompt']
                print(f"     {preview}\n")

    elif choice == "2":
        shot = input("Describe your shot: ")
        platform = input("Platform (Kling / Runway / Veo): ")
        valid = ["Kling", "Runway", "Veo"]
        if platform.strip().title() not in valid:
            print(f"  Unknown platform. Choose: {', '.join(valid)}\n")
            continue
        platform = platform.strip().title()
        try:
            text = generate_prompt_text(shot, platform)
            print(f"\n{text}\n")
            if input("Save? (y/n): ").lower() == "y":
                new_id = db.add_prompt(platform, shot, text)
                print(f"Saved as #{new_id}!\n")
        except Exception as e:
            print(f"  API error: {e}\n")

    elif choice == "3":
        query = input("Search: ")
        results = db.search(query)
        print(f"\n  {len(results)} match{'es' if len(results) != 1 else ''}")
        for row in results:
            print(f"    #{row['id']} [{row['platform']}] {row['shot']}")
        print()

    elif choice == "4":
        platform = input("Filter by platform: ")
        results = db.get_by_platform(platform)
        print(f"\n  {len(results)} {platform} prompts")
        for row in results:
            print(f"    #{row['id']} {row['shot']}")
        print()

    elif choice == "5":
        prompt_id = input("Delete prompt ID # (or 'cancel'): ")
        if prompt_id.lower() != "cancel":
            try:
                if db.delete(int(prompt_id)):
                    print(f"  Deleted #{prompt_id}\n")
                else:
                    print(f"  No prompt with ID {prompt_id}\n")
            except ValueError:
                print("  Enter a number.\n")

    elif choice == "6":
        counts = db.count_by_platform()
        total = sum(counts.values())
        print(f"\n  Total: {total} prompts")
        for platform, count in counts.items():
            print(f"    {platform}: {count}")
        print()

    elif choice == "7":
        db.close()
        print("Bye!")
        break

Deletion by ID instead of index is a significant improvement. The old code asked "delete #3" meaning the 3rd item in a list — which changed every time you added or deleted something. Now you delete by the stable database ID. #5 is always #5 regardless of what else is in the library.

Rows are sqlite3.Row objects, not Prompt instances. You access values with row['platform'] (dictionary syntax) instead of p.platform (attribute syntax). If you want Prompt objects, you can convert them: Prompt.from_dict(dict(row)).

What Changed — and Why It's Better

End of Day Checklist

Tomorrow

Day 55 writes the migration script that moves your existing prompts.json data into the new SQLite database — a one-time operation that brings your history along.