from sqlalchemy import Column, String, Text, DateTime, Integer, Boolean, ForeignKey, JSON from sqlalchemy.orm import relationship from datetime import datetime, timezone from database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String, unique=True, nullable=False) email = Column(String, unique=True, nullable=True) password_hash = Column(String, nullable=False) display_name = Column(String, default="") role = Column(String, default="user") # admin or user created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) instances = relationship("AgentInstance", back_populates="user") class AgentCatalog(Base): __tablename__ = "agent_catalog" id = Column(String, primary_key=True) name = Column(String, nullable=False) description = Column(Text, default="") category = Column(String, default="utility") # data, briefing, utility config_schema = Column(JSON, default=dict) default_config = Column(JSON, default=dict) supports_schedule = Column(Boolean, default=True) is_sub_agent = Column(Boolean, default=False) instances = relationship("AgentInstance", back_populates="catalog_entry") class AgentInstance(Base): __tablename__ = "agent_instances" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) catalog_id = Column(String, ForeignKey("agent_catalog.id"), nullable=False) name = Column(String, nullable=False) config = Column(JSON, default=dict) schedule = Column(String, default="manual") status = Column(String, default="active") created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) user = relationship("User", back_populates="instances") catalog_entry = relationship("AgentCatalog", back_populates="instances") runs = relationship("Run", back_populates="instance", order_by="Run.started_at.desc()") class Run(Base): __tablename__ = "runs" id = Column(Integer, primary_key=True, autoincrement=True) instance_id = Column(Integer, ForeignKey("agent_instances.id"), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) started_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) finished_at = Column(DateTime, nullable=True) status = Column(String, default="running") output = Column(Text, default="") error = Column(Text, default="") metadata_ = Column("metadata", JSON, default=dict) instance = relationship("AgentInstance", back_populates="runs") class Bridge(Base): __tablename__ = "bridges" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) api_key = Column(String, nullable=False) # Auth token for bridge requests bridge_url = Column(String, default="") # http://ip:port hostname = Column(String, default="") # e.g. "Jungbauers-MBP" platform = Column(String, default="macos") # macos, ios (future) capabilities = Column(JSON, default=list) # ["notes", "reading-list"] status = Column(String, default="offline") # online, offline last_heartbeat = Column(DateTime, nullable=True) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) user = relationship("User") class LLMProvider(Base): __tablename__ = "llm_providers" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False) provider_type = Column(String, default="anthropic") # anthropic, openai, litellm, ollama api_url = Column(String, default="") api_key = Column(String, default="") default_model = Column(String, default="") is_default = Column(Boolean, default=False)