CmdForge/src/cmdforge/profiles.py

178 lines
7.0 KiB
Python

"""AI persona profiles for prompt injection."""
import os
from dataclasses import dataclass, field
from typing import Optional, List
from pathlib import Path
import yaml
from .config import get_config_dir
@dataclass
class Profile:
"""An AI persona profile with system instructions."""
name: str
description: str = ""
system_prompt: str = ""
tags: List[str] = field(default_factory=list)
builtin: bool = False # True for default profiles
def to_dict(self) -> dict:
d = {
"name": self.name,
"description": self.description,
"system_prompt": self.system_prompt,
}
if self.tags:
d["tags"] = self.tags
return d
@classmethod
def from_dict(cls, data: dict, builtin: bool = False) -> "Profile":
return cls(
name=data.get("name", ""),
description=data.get("description", ""),
system_prompt=data.get("system_prompt", ""),
tags=data.get("tags", []),
builtin=builtin
)
# Built-in default profiles
DEFAULT_PROFILES = [
Profile(
name="None",
description="No profile - use prompt as-is",
system_prompt="",
builtin=True
),
Profile(
name="Comedian",
description="Witty and humorous, but stays informative",
system_prompt="""You are a witty and humorous assistant who uses humor to make information more engaging and memorable. You're able to be serious when it matters, but you naturally inject levity and clever observations into your responses. You are an expert at explaining things clearly while keeping the reader entertained.""",
tags=["creative", "engaging"],
builtin=True
),
Profile(
name="Technical Writer",
description="Precise, structured, uses proper terminology",
system_prompt="""You are a professional technical writer who prioritizes clarity, precision, and proper structure. You use correct terminology, organize information logically with headers and bullet points when appropriate, and ensure accuracy in all technical details. You write documentation that is both comprehensive and accessible.""",
tags=["technical", "documentation"],
builtin=True
),
Profile(
name="Teacher",
description="Patient, explains step-by-step, uses analogies",
system_prompt="""You are a patient and experienced teacher who excels at breaking down complex topics into understandable parts. You explain concepts step-by-step, use relatable analogies and examples, and check for understanding. You adapt your explanations to the learner's level and never make them feel bad for not knowing something.""",
tags=["educational", "patient"],
builtin=True
),
Profile(
name="Concise",
description="Brief responses, bullet points, no fluff",
system_prompt="""You are a direct and efficient communicator who values brevity. You get straight to the point, use bullet points and short sentences, and avoid unnecessary filler words or lengthy explanations. Every word you write serves a purpose. You provide complete information in the most compact form possible.""",
tags=["brief", "efficient"],
builtin=True
),
Profile(
name="Creative",
description="Imaginative, thinks outside the box",
system_prompt="""You are a highly creative thinker who approaches problems from unexpected angles. You generate novel ideas, make unique connections between concepts, and aren't afraid to suggest unconventional solutions. You balance creativity with practicality, ensuring your ideas are both innovative and actionable.""",
tags=["creative", "innovative"],
builtin=True
),
Profile(
name="Code Reviewer",
description="Critical eye, suggests improvements, follows best practices",
system_prompt="""You are an experienced code reviewer with a keen eye for quality. You identify potential bugs, security issues, and performance problems. You suggest improvements based on best practices and design patterns. You explain the reasoning behind your suggestions and prioritize the most impactful changes. You're constructive, not harsh.""",
tags=["developer", "code"],
builtin=True
),
Profile(
name="Analyst",
description="Data-driven, objective, thorough analysis",
system_prompt="""You are a meticulous analyst who examines information thoroughly and objectively. You consider multiple perspectives, identify patterns and trends, and support your conclusions with evidence. You present findings in a clear, structured manner and acknowledge uncertainties or limitations in your analysis.""",
tags=["analytical", "objective"],
builtin=True
),
]
def get_profiles_dir() -> Path:
"""Get the profiles directory path."""
profiles_dir = Path(get_config_dir()) / "profiles"
profiles_dir.mkdir(parents=True, exist_ok=True)
return profiles_dir
def load_profile(name: str) -> Optional[Profile]:
"""Load a profile by name."""
# Check built-in profiles first
for profile in DEFAULT_PROFILES:
if profile.name.lower() == name.lower():
return profile
# Check user profiles
profile_path = get_profiles_dir() / f"{name}.yaml"
if profile_path.exists():
try:
with open(profile_path) as f:
data = yaml.safe_load(f)
return Profile.from_dict(data)
except Exception:
return None
return None
def save_profile(profile: Profile) -> None:
"""Save a user profile."""
if profile.builtin:
raise ValueError("Cannot save built-in profiles")
profile_path = get_profiles_dir() / f"{profile.name}.yaml"
with open(profile_path, "w") as f:
yaml.safe_dump(profile.to_dict(), f, default_flow_style=False)
def delete_profile(name: str) -> bool:
"""Delete a user profile."""
# Can't delete built-in profiles
for profile in DEFAULT_PROFILES:
if profile.name.lower() == name.lower():
return False
profile_path = get_profiles_dir() / f"{name}.yaml"
if profile_path.exists():
profile_path.unlink()
return True
return False
def list_profiles() -> List[Profile]:
"""List all available profiles (built-in + user)."""
profiles = list(DEFAULT_PROFILES)
# Load user profiles
profiles_dir = get_profiles_dir()
if profiles_dir.exists():
for path in profiles_dir.glob("*.yaml"):
try:
with open(path) as f:
data = yaml.safe_load(f)
profile = Profile.from_dict(data)
# Don't add duplicates of built-in names
if not any(p.name.lower() == profile.name.lower() for p in profiles):
profiles.append(profile)
except Exception:
continue
return profiles
def get_profile_names() -> List[str]:
"""Get list of all profile names."""
return [p.name for p in list_profiles()]