Build dvp.py — a proper command-line tool that wraps all your Prompt Vault functionality.
Day 73 of 80
You're building a single CLI tool that replaces all your separate scripts. Instead of remembering which file does what, you run:
python dvp.py list # see all prompts
python dvp.py generate "Golf swing at sunset" # generate for all platforms
python dvp.py search golf # find prompts by keyword
python dvp.py delete 5 # remove a specific prompt
python dvp.py stats # platform breakdown
One entry point. All functionality. Clean help text on every command.
Create dvp.py in your prompt-vault/ folder:
# dvp.py — the DVP Prompt Vault CLI tool.
# Run: python dvp.py --help
import asyncio
import json
import typer
import anthropic
from dotenv import load_dotenv
from pathlib import Path
from database import Database
from logger import get_logger
load_dotenv()
logger = get_logger(__name__)
app = typer.Typer(
name="dvp",
help="DVP Prompt Vault — manage and generate AI video prompts.",
)
# Open the database once. It stays open for the duration of the command.
db = Database("prompt_vault.db")
@app.command()
def list(
platform: str = typer.Option(None, "--platform", "-p", help="Filter by platform"),
):
"""List all saved prompts, optionally filtered by platform."""
if platform:
rows = db.get_by_platform(platform)
typer.echo(f"\n [{platform}] — {len(rows)} prompts\n")
else:
rows = db.get_all()
typer.echo(f"\n All prompts — {len(rows)} total\n")
if not rows:
typer.echo(" (empty — generate some prompts first)")
return
for row in rows:
typer.echo(f" #{row['id']} [{row['platform']}] {row['shot']}")
preview = row["prompt"][:70] + "..." if len(row["prompt"]) > 70 else row["prompt"]
typer.echo(f" {preview}\n")
@app.command()
def add(
platform: str = typer.Argument(..., help="Platform (Kling/Runway/Veo)"),
shot: str = typer.Argument(..., help="Shot description"),
prompt_text: str = typer.Argument(..., help="Full prompt text"),
):
"""Add a prompt manually to the vault."""
valid = ["Kling", "Runway", "Veo"]
cleaned = platform.strip().title()
if cleaned not in valid:
typer.echo(f" Unknown platform '{platform}'. Valid: {', '.join(valid)}")
raise typer.Exit(1)
prompt_id = db.add_prompt(cleaned, shot, prompt_text)
typer.echo(f" Added prompt #{prompt_id}: [{cleaned}] {shot}")
@app.command()
def search(
query: str = typer.Argument(..., help="Search keyword"),
):
"""Search prompts by keyword (shot description or prompt text)."""
rows = db.search(query)
typer.echo(f"\n Found {len(rows)} match{'es' if len(rows) != 1 else ''} for '{query}':\n")
for row in rows:
typer.echo(f" #{row['id']} [{row['platform']}] {row['shot']}")
typer.echo()
@app.command()
def delete(
prompt_id: int = typer.Argument(..., help="Prompt ID to delete"),
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
):
"""Delete a prompt by ID."""
if not force:
confirmed = typer.confirm(f"Delete prompt #{prompt_id}?")
if not confirmed:
typer.echo("Cancelled.")
return
if db.delete(prompt_id):
typer.echo(f" Deleted prompt #{prompt_id}")
else:
typer.echo(f" No prompt with ID {prompt_id}")
raise typer.Exit(1)
@app.command()
def stats():
"""Show prompt library statistics."""
counts = db.count_by_platform()
total = sum(counts.values())
typer.echo(f"\n DVP Prompt Vault — {total} prompts total\n")
for platform, count in sorted(counts.items()):
bar = "█" * count
typer.echo(f" {platform:8} {bar} {count}")
typer.echo()
if __name__ == "__main__":
app()
typer.Argument(...) — the ... means "required." Compare this to typer.Option(None) which has None as a default (making it optional). The ellipsis is Python's standard "required" marker.
raise typer.Exit(1) exits with a non-zero status code. In shell scripting, exit code 0 means success, anything else means failure. This lets other tools detect if your command failed.
The stats bar uses Unicode block character █ repeated count times. Simple but effective — you can see the distribution at a glance.
python dvp.py --help
python dvp.py list
python dvp.py search golf
python dvp.py stats
python dvp.py delete 1 # will ask for confirmation
python dvp.py delete 1 -f # skips confirmation
dvp.py created with list, add, search, delete, statspython dvp.py COMMAND --help works)delete confirms before deleting unless --force is passedstats shows the bar chartDay 74 adds the generate and batch commands — the async generation commands that make this tool genuinely useful for your workflow.