Files
discord-fishbowl/install.py
matt 1b586582d4 Implement comprehensive collaborative creative system with cross-character memory sharing
Major Features Added:
• Cross-character memory sharing with trust-based permissions (Basic 30%, Personal 50%, Intimate 70%, Full 90%)
• Complete collaborative creative projects system with MCP integration
• Database persistence for all creative project data with proper migrations
• Trust evolution system based on interaction quality and relationship development
• Memory sharing MCP server with 6 autonomous tools for character decision-making
• Creative projects MCP server with 8 tools for autonomous project management
• Enhanced character integration with all RAG and MCP capabilities
• Demo scripts showcasing memory sharing and creative collaboration workflows

System Integration:
• Main application now initializes memory sharing and creative managers
• Conversation engine upgraded to use EnhancedCharacter objects with full RAG access
• Database models added for creative projects, collaborators, contributions, and invitations
• Complete prompt construction pipeline enriched with RAG insights and trust data
• Characters can now autonomously propose projects, share memories, and collaborate creatively
2025-07-04 23:07:08 -07:00

724 lines
29 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Discord Fishbowl Interactive Setup Script
Comprehensive installation and configuration wizard
"""
import os
import sys
import subprocess
import platform
import json
import secrets
import getpass
from pathlib import Path
from typing import Dict, Any, Optional
class FishbowlSetup:
def __init__(self):
self.project_root = Path(__file__).parent
self.venv_path = self.project_root / "venv"
self.config_path = self.project_root / "config"
self.env_file = self.project_root / ".env"
self.config_file = self.config_path / "fishbowl_config.json"
self.python_executable = None
self.config = {}
def print_header(self):
"""Print welcome header"""
print("\n" + "=" * 80)
print("🐠 DISCORD FISHBOWL INTERACTIVE SETUP")
print("Autonomous Character Ecosystem Installation Wizard")
print("=" * 80 + "\n")
def print_section(self, title: str):
"""Print section header"""
print(f"\n🔧 {title}")
print("-" * (len(title) + 3))
def print_success(self, message: str):
"""Print success message"""
print(f"{message}")
def print_error(self, message: str):
"""Print error message"""
print(f"{message}")
def print_warning(self, message: str):
"""Print warning message"""
print(f"⚠️ {message}")
def print_info(self, message: str):
"""Print info message"""
print(f" {message}")
def ask_question(self, question: str, default: str = None, required: bool = True, secret: bool = False) -> str:
"""Ask user a question with optional default"""
if default:
prompt = f"{question} [{default}]: "
else:
prompt = f"{question}: "
while True:
if secret:
answer = getpass.getpass(prompt).strip()
else:
answer = input(prompt).strip()
if answer:
return answer
elif default:
return default
elif not required:
return ""
else:
self.print_error("This field is required. Please enter a value.")
def ask_yes_no(self, question: str, default: bool = True) -> bool:
"""Ask yes/no question"""
default_str = "Y/n" if default else "y/N"
answer = input(f"{question} [{default_str}]: ").strip().lower()
if not answer:
return default
return answer in ['y', 'yes', 'true', '1']
def ask_choice(self, question: str, choices: list, default: int = 0) -> str:
"""Ask user to choose from a list"""
print(f"\n{question}")
for i, choice in enumerate(choices, 1):
marker = "" if i == default + 1 else " "
print(f" {marker} {i}. {choice}")
while True:
try:
answer = input(f"Choose [1-{len(choices)}] (default: {default + 1}): ").strip()
if not answer:
return choices[default]
choice_num = int(answer) - 1
if 0 <= choice_num < len(choices):
return choices[choice_num]
else:
self.print_error(f"Please choose a number between 1 and {len(choices)}")
except ValueError:
self.print_error("Please enter a valid number")
def check_python_version(self):
"""Check Python version compatibility"""
self.print_section("Checking Python Version")
major, minor = sys.version_info[:2]
if major < 3 or (major == 3 and minor < 8):
self.print_error(f"Python 3.8+ required. Found Python {major}.{minor}")
self.print_info("Please install Python 3.8 or higher and run this script again.")
sys.exit(1)
self.print_success(f"Python {major}.{minor} is compatible")
def check_system_dependencies(self):
"""Check system dependencies"""
self.print_section("Checking System Dependencies")
# Check for Git
try:
result = subprocess.run(["git", "--version"], check=True, capture_output=True, text=True)
self.print_success(f"Git found: {result.stdout.strip()}")
except (subprocess.CalledProcessError, FileNotFoundError):
self.print_error("Git is required but not found.")
self.print_info("Please install Git from https://git-scm.com/")
sys.exit(1)
# Check for Node.js (optional, for frontend)
try:
result = subprocess.run(["node", "--version"], check=True, capture_output=True, text=True)
version = result.stdout.strip()
self.print_success(f"Node.js found: {version}")
# Check npm
npm_result = subprocess.run(["npm", "--version"], check=True, capture_output=True, text=True)
self.print_success(f"npm found: {npm_result.stdout.strip()}")
except (subprocess.CalledProcessError, FileNotFoundError):
self.print_warning("Node.js/npm not found. Admin frontend won't be available.")
if not self.ask_yes_no("Continue without frontend support?", True):
self.print_info("Please install Node.js from https://nodejs.org/")
sys.exit(1)
def create_virtual_environment(self):
"""Create Python virtual environment"""
self.print_section("Setting Up Python Virtual Environment")
if self.venv_path.exists():
if self.ask_yes_no("Virtual environment already exists. Recreate it?", False):
import shutil
shutil.rmtree(self.venv_path)
else:
self.print_info("Using existing virtual environment")
self.python_executable = self.get_venv_python()
return
self.print_info("Creating virtual environment...")
try:
subprocess.run([sys.executable, "-m", "venv", str(self.venv_path)],
check=True, capture_output=True)
self.print_success("Virtual environment created successfully")
self.python_executable = self.get_venv_python()
except subprocess.CalledProcessError as e:
self.print_error(f"Failed to create virtual environment: {e}")
sys.exit(1)
def get_venv_python(self) -> str:
"""Get path to Python executable in virtual environment"""
if platform.system() == "Windows":
return str(self.venv_path / "Scripts" / "python.exe")
else:
return str(self.venv_path / "bin" / "python")
def install_python_dependencies(self):
"""Install Python dependencies"""
self.print_section("Installing Python Dependencies")
self.print_info("Upgrading pip...")
try:
subprocess.run([
self.python_executable, "-m", "pip", "install", "--upgrade", "pip"
], check=True, capture_output=True)
self.print_info("Installing project dependencies...")
subprocess.run([
self.python_executable, "-m", "pip", "install", "-r", "requirements.txt"
], check=True)
self.print_success("All Python dependencies installed successfully")
except subprocess.CalledProcessError as e:
self.print_error(f"Failed to install dependencies: {e}")
self.print_info("Try running manually: pip install -r requirements.txt")
sys.exit(1)
def setup_frontend_dependencies(self):
"""Set up frontend dependencies"""
self.print_section("Setting Up Admin Frontend")
frontend_path = self.project_root / "admin-frontend"
if not frontend_path.exists():
self.print_warning("Frontend directory not found. Skipping frontend setup.")
return
if not self.ask_yes_no("Install admin frontend? (requires Node.js)", True):
self.print_info("Skipping frontend installation")
return
self.print_info("Installing frontend dependencies...")
try:
os.chdir(frontend_path)
subprocess.run(["npm", "install"], check=True, capture_output=True)
self.print_success("Frontend dependencies installed")
if self.ask_yes_no("Build frontend for production?", False):
self.print_info("Building frontend...")
subprocess.run(["npm", "run", "build"], check=True, capture_output=True)
self.print_success("Frontend built for production")
except subprocess.CalledProcessError as e:
self.print_error(f"Frontend setup failed: {e}")
self.print_warning("You can set up the frontend manually later")
except FileNotFoundError:
self.print_warning("npm not found. Install Node.js to enable frontend")
finally:
os.chdir(self.project_root)
def collect_discord_config(self):
"""Collect Discord configuration"""
print("\n📱 Discord Bot Configuration")
print("You'll need to create a Discord application and bot at:")
print("https://discord.com/developers/applications")
print()
self.config["discord"] = {
"bot_token": self.ask_question("Discord Bot Token", secret=True),
"guild_id": self.ask_question("Discord Server (Guild) ID"),
"channel_id": self.ask_question("Discord Channel ID for conversations"),
}
def collect_database_config(self):
"""Collect database configuration"""
print("\n🗄️ Database Configuration")
db_choices = ["SQLite (simple, file-based)", "PostgreSQL (recommended for production)"]
db_choice = self.ask_choice("Choose database type:", db_choices, 0)
if "PostgreSQL" in db_choice:
self.config["database"] = {
"type": "postgresql",
"host": self.ask_question("PostgreSQL host", "localhost"),
"port": int(self.ask_question("PostgreSQL port", "5432")),
"name": self.ask_question("Database name", "discord_fishbowl"),
"username": self.ask_question("Database username", "postgres"),
"password": self.ask_question("Database password", secret=True),
}
else:
self.config["database"] = {
"type": "sqlite",
"path": "data/fishbowl.db"
}
self.print_info("SQLite database will be created automatically")
def collect_redis_config(self):
"""Collect Redis configuration"""
print("\n🔴 Redis Configuration")
self.print_info("Redis is used for caching and pub/sub messaging")
if self.ask_yes_no("Use Redis? (recommended)", True):
self.config["redis"] = {
"enabled": True,
"host": self.ask_question("Redis host", "localhost"),
"port": int(self.ask_question("Redis port", "6379")),
"password": self.ask_question("Redis password (leave empty if none)", "", required=False),
"db": int(self.ask_question("Redis database number", "0")),
}
else:
self.config["redis"] = {"enabled": False}
def collect_vector_db_config(self):
"""Collect vector database configuration"""
print("\n🔍 Vector Database Configuration")
self.print_info("Vector database stores character memories and enables semantic search")
vector_choices = ["Qdrant (recommended)", "In-memory (for testing)", "Skip vector database"]
vector_choice = self.ask_choice("Choose vector database:", vector_choices, 0)
if "Qdrant" in vector_choice:
self.config["vector_db"] = {
"type": "qdrant",
"host": self.ask_question("Qdrant host", "localhost"),
"port": int(self.ask_question("Qdrant port", "6333")),
"collection_name": self.ask_question("Collection name", "fishbowl_memories"),
}
elif "In-memory" in vector_choice:
self.config["vector_db"] = {"type": "memory"}
self.print_warning("In-memory vector database won't persist between restarts")
else:
self.config["vector_db"] = {"type": "none"}
self.print_warning("Without vector database, character memories will be limited")
def collect_ai_config(self):
"""Collect AI provider configuration"""
print("\n🤖 AI Provider Configuration")
ai_choices = ["OpenAI (GPT models)", "Anthropic (Claude models)", "Local/Custom API"]
ai_choice = self.ask_choice("Choose AI provider:", ai_choices, 0)
if "OpenAI" in ai_choice:
self.config["ai"] = {
"provider": "openai",
"api_key": self.ask_question("OpenAI API Key", secret=True),
"model": self.ask_question("Model name", "gpt-4"),
"max_tokens": int(self.ask_question("Max tokens per response", "2000")),
"temperature": float(self.ask_question("Temperature (0.0-2.0)", "0.8")),
}
elif "Anthropic" in ai_choice:
self.config["ai"] = {
"provider": "anthropic",
"api_key": self.ask_question("Anthropic API Key", secret=True),
"model": self.ask_question("Model name", "claude-3-sonnet-20240229"),
"max_tokens": int(self.ask_question("Max tokens per response", "2000")),
"temperature": float(self.ask_question("Temperature (0.0-1.0)", "0.8")),
}
else:
self.config["ai"] = {
"provider": "custom",
"api_base": self.ask_question("API Base URL"),
"api_key": self.ask_question("API Key", "", required=False, secret=True),
"model": self.ask_question("Model name"),
"max_tokens": int(self.ask_question("Max tokens per response", "2000")),
}
def collect_system_config(self):
"""Collect system configuration"""
print("\n⚙️ System Configuration")
self.config["system"] = {
"conversation_frequency": float(self.ask_question("Conversation frequency (0.1-1.0, higher=more active)", "0.5")),
"response_delay_min": float(self.ask_question("Minimum response delay (seconds)", "1.0")),
"response_delay_max": float(self.ask_question("Maximum response delay (seconds)", "5.0")),
"memory_retention_days": int(self.ask_question("Memory retention days", "90")),
"max_conversation_length": int(self.ask_question("Max conversation length (messages)", "50")),
"creativity_boost": self.ask_yes_no("Enable creativity boost?", True),
"safety_monitoring": self.ask_yes_no("Enable safety monitoring?", True),
"auto_moderation": self.ask_yes_no("Enable auto-moderation?", False),
"personality_change_rate": float(self.ask_question("Personality change rate (0.0-1.0)", "0.1")),
}
def collect_admin_config(self):
"""Collect admin interface configuration"""
print("\n🔐 Admin Interface Configuration")
self.config["admin"] = {
"enabled": self.ask_yes_no("Enable admin web interface?", True),
"host": self.ask_question("Admin interface host", "127.0.0.1"),
"port": int(self.ask_question("Admin interface port", "8000")),
"secret_key": secrets.token_urlsafe(32),
"admin_username": self.ask_question("Admin username", "admin"),
"admin_password": self.ask_question("Admin password", secret=True),
}
def collect_configuration(self):
"""Collect all configuration from user"""
self.print_section("Configuration Setup")
self.print_info("We'll now collect configuration for all components")
self.collect_discord_config()
self.collect_database_config()
self.collect_redis_config()
self.collect_vector_db_config()
self.collect_ai_config()
self.collect_system_config()
self.collect_admin_config()
def create_config_files(self):
"""Create configuration files"""
self.print_section("Creating Configuration Files")
# Create config directory
self.config_path.mkdir(exist_ok=True)
# Save JSON configuration
with open(self.config_file, "w") as f:
json.dump(self.config, f, indent=2)
self.print_success(f"Configuration saved to {self.config_file}")
# Create .env file
env_content = self.create_env_content()
with open(self.env_file, "w") as f:
f.write(env_content)
self.print_success(f"Environment variables saved to {self.env_file}")
# Create necessary directories
(self.project_root / "data").mkdir(exist_ok=True)
(self.project_root / "logs").mkdir(exist_ok=True)
self.print_success("Required directories created")
def create_env_content(self) -> str:
"""Create .env file content"""
lines = [
"# Discord Fishbowl Configuration",
"# Generated by interactive setup script",
"",
"# Discord",
f"DISCORD_BOT_TOKEN={self.config['discord']['bot_token']}",
f"DISCORD_GUILD_ID={self.config['discord']['guild_id']}",
f"DISCORD_CHANNEL_ID={self.config['discord']['channel_id']}",
"",
]
# Database
db_config = self.config["database"]
if db_config["type"] == "postgresql":
db_url = f"postgresql://{db_config['username']}:{db_config['password']}@{db_config['host']}:{db_config['port']}/{db_config['name']}"
else:
db_url = f"sqlite:///{db_config['path']}"
lines.extend([
"# Database",
f"DATABASE_URL={db_url}",
"",
])
# Redis
if self.config["redis"]["enabled"]:
redis_config = self.config["redis"]
if redis_config.get("password"):
redis_url = f"redis://:{redis_config['password']}@{redis_config['host']}:{redis_config['port']}/{redis_config['db']}"
else:
redis_url = f"redis://{redis_config['host']}:{redis_config['port']}/{redis_config['db']}"
lines.extend([
"# Redis",
f"REDIS_URL={redis_url}",
"",
])
# Vector Database
vector_config = self.config["vector_db"]
if vector_config["type"] == "qdrant":
lines.extend([
"# Vector Database",
f"QDRANT_HOST={vector_config['host']}",
f"QDRANT_PORT={vector_config['port']}",
f"QDRANT_COLLECTION={vector_config['collection_name']}",
"",
])
# AI Provider
ai_config = self.config["ai"]
lines.extend([
"# AI Provider",
f"AI_PROVIDER={ai_config['provider']}",
f"AI_API_KEY={ai_config['api_key']}",
f"AI_MODEL={ai_config['model']}",
f"AI_MAX_TOKENS={ai_config['max_tokens']}",
])
if "temperature" in ai_config:
lines.append(f"AI_TEMPERATURE={ai_config['temperature']}")
if "api_base" in ai_config:
lines.append(f"AI_API_BASE={ai_config['api_base']}")
lines.append("")
# Admin Interface
if self.config["admin"]["enabled"]:
admin_config = self.config["admin"]
lines.extend([
"# Admin Interface",
f"ADMIN_HOST={admin_config['host']}",
f"ADMIN_PORT={admin_config['port']}",
f"SECRET_KEY={admin_config['secret_key']}",
f"ADMIN_USERNAME={admin_config['admin_username']}",
f"ADMIN_PASSWORD={admin_config['admin_password']}",
"",
])
# System Configuration
system_config = self.config["system"]
lines.extend([
"# System Configuration",
f"CONVERSATION_FREQUENCY={system_config['conversation_frequency']}",
f"RESPONSE_DELAY_MIN={system_config['response_delay_min']}",
f"RESPONSE_DELAY_MAX={system_config['response_delay_max']}",
f"MEMORY_RETENTION_DAYS={system_config['memory_retention_days']}",
f"MAX_CONVERSATION_LENGTH={system_config['max_conversation_length']}",
f"CREATIVITY_BOOST={str(system_config['creativity_boost']).lower()}",
f"SAFETY_MONITORING={str(system_config['safety_monitoring']).lower()}",
f"AUTO_MODERATION={str(system_config['auto_moderation']).lower()}",
f"PERSONALITY_CHANGE_RATE={system_config['personality_change_rate']}",
"",
"# Environment",
"ENVIRONMENT=production",
"LOG_LEVEL=INFO",
])
return "\n".join(lines)
def setup_database_schema(self):
"""Set up database schema"""
self.print_section("Setting Up Database Schema")
if self.config["database"]["type"] == "sqlite":
# Ensure data directory exists
(self.project_root / "data").mkdir(exist_ok=True)
self.print_info("Initializing database schema...")
try:
# Run database migrations
subprocess.run([
self.python_executable, "-m", "alembic", "upgrade", "head"
], check=True, cwd=self.project_root, capture_output=True)
self.print_success("Database schema initialized")
except subprocess.CalledProcessError as e:
self.print_warning("Database migration failed - you may need to set it up manually")
self.print_info("Run: python -m alembic upgrade head")
def create_startup_scripts(self):
"""Create convenient startup scripts"""
self.print_section("Creating Startup Scripts")
# Main application script (Unix)
if platform.system() != "Windows":
run_script = f"""#!/bin/bash
# Discord Fishbowl - Main Application
echo "🐠 Starting Discord Fishbowl..."
# Activate virtual environment
source "{self.venv_path}/bin/activate"
# Set Python path
export PYTHONPATH="{self.project_root}:$PYTHONPATH"
# Load environment variables
if [ -f "{self.env_file}" ]; then
export $(cat "{self.env_file}" | xargs)
fi
# Start the application
python -m src.main "$@"
"""
script_path = self.project_root / "start.sh"
with open(script_path, "w") as f:
f.write(run_script)
script_path.chmod(0o755)
# Admin interface script (Unix)
if self.config["admin"]["enabled"]:
admin_script = f"""#!/bin/bash
# Discord Fishbowl - Admin Interface
echo "🌐 Starting Admin Interface..."
# Activate virtual environment
source "{self.venv_path}/bin/activate"
# Set Python path
export PYTHONPATH="{self.project_root}:$PYTHONPATH"
# Load environment variables
if [ -f "{self.env_file}" ]; then
export $(cat "{self.env_file}" | xargs)
fi
# Start admin interface
python -m src.admin.app
"""
admin_path = self.project_root / "start-admin.sh"
with open(admin_path, "w") as f:
f.write(admin_script)
admin_path.chmod(0o755)
# Windows batch files
if platform.system() == "Windows":
run_bat = f"""@echo off
echo 🐠 Starting Discord Fishbowl...
call "{self.venv_path}\\Scripts\\activate.bat"
set PYTHONPATH={self.project_root};%PYTHONPATH%
python -m src.main %*
"""
with open(self.project_root / "start.bat", "w") as f:
f.write(run_bat)
if self.config["admin"]["enabled"]:
admin_bat = f"""@echo off
echo 🌐 Starting Admin Interface...
call "{self.venv_path}\\Scripts\\activate.bat"
set PYTHONPATH={self.project_root};%PYTHONPATH%
python -m src.admin.app
"""
with open(self.project_root / "start-admin.bat", "w") as f:
f.write(admin_bat)
self.print_success("Startup scripts created")
def create_character_configs(self):
"""Create initial character configurations"""
self.print_section("Setting Up Initial Characters")
if self.ask_yes_no("Create default character configurations?", True):
try:
subprocess.run([
self.python_executable, "-m", "scripts.init_characters"
], check=True, cwd=self.project_root, capture_output=True)
self.print_success("Default characters initialized")
except subprocess.CalledProcessError:
self.print_warning("Character initialization failed - you can run it manually later")
self.print_info("Run: python -m scripts.init_characters")
def print_completion_summary(self):
"""Print setup completion summary"""
self.print_section("🎉 Setup Complete!")
print("Discord Fishbowl has been successfully installed and configured!")
print()
# Prerequisites reminder
print("📋 Before starting, ensure these services are running:")
if self.config["database"]["type"] == "postgresql":
print(" • PostgreSQL database server")
if self.config["redis"]["enabled"]:
print(" • Redis server")
if self.config["vector_db"]["type"] == "qdrant":
print(" • Qdrant vector database")
print()
# How to start
print("🚀 To start the fishbowl:")
if platform.system() == "Windows":
print(" > start.bat")
else:
print(" $ ./start.sh")
print()
# Admin interface
if self.config["admin"]["enabled"]:
print("🌐 To start the admin interface:")
if platform.system() == "Windows":
print(" > start-admin.bat")
else:
print(" $ ./start-admin.sh")
print()
print(f" Admin interface will be available at:")
print(f" http://{self.config['admin']['host']}:{self.config['admin']['port']}/admin")
print(f" Login: {self.config['admin']['admin_username']}")
print()
# Important files
print("📁 Important files:")
print(f" Configuration: {self.config_file}")
print(f" Environment: {self.env_file}")
print(f" Virtual Env: {self.venv_path}")
print()
# Troubleshooting
print("🔧 Troubleshooting:")
print(" • Check logs in the logs/ directory")
print(" • Verify all API keys and credentials")
print(" • Ensure external services are accessible")
print(" • See README.md for detailed documentation")
print()
self.print_success("Happy fishing! 🐠✨")
def run(self):
"""Run the complete setup process"""
try:
self.print_header()
# Pre-flight checks
self.check_python_version()
self.check_system_dependencies()
# Environment setup
self.create_virtual_environment()
self.install_python_dependencies()
self.setup_frontend_dependencies()
# Configuration
self.collect_configuration()
self.create_config_files()
# Database and scripts
self.setup_database_schema()
self.create_startup_scripts()
self.create_character_configs()
# Final summary
self.print_completion_summary()
except KeyboardInterrupt:
print("\n\n⚠️ Setup interrupted by user")
print("You can run this script again to resume setup.")
sys.exit(1)
except Exception as e:
self.print_error(f"Setup failed: {e}")
self.print_info("Please check the error and run the script again.")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help"]:
print("Discord Fishbowl Interactive Setup Script")
print("Usage: python install.py")
print()
print("This script will guide you through setting up the Discord Fishbowl")
print("autonomous character ecosystem with all required dependencies")
print("and configuration.")
sys.exit(0)
setup = FishbowlSetup()
setup.run()