Major WHOOSH system refactoring and feature enhancements

- Migrated from HIVE branding to WHOOSH across all components
- Enhanced backend API with new services: AI models, BZZZ integration, templates, members
- Added comprehensive testing suite with security, performance, and integration tests
- Improved frontend with new components for project setup, AI models, and team management
- Updated MCP server implementation with WHOOSH-specific tools and resources
- Enhanced deployment configurations with production-ready Docker setups
- Added comprehensive documentation and setup guides
- Implemented age encryption service and UCXL integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-08-27 08:34:48 +10:00
parent 0e9844ef13
commit 268214d971
399 changed files with 57390 additions and 2045 deletions

View File

@@ -1,5 +1,5 @@
"""
Project Service for integrating with local project directories and GitHub.
Project Service for integrating with local project directories and GITEA.
"""
import os
import json
@@ -15,11 +15,11 @@ from app.models.project import Project
class ProjectService:
def __init__(self):
self.projects_base_path = Path("/home/tony/AI/projects")
self.github_token = self._get_github_token()
self.github_api_base = "https://api.github.com"
self.gitea_token = self._get_gitea_token()
self.gitea_api_base = "http://ironwood:3000/api/v1"
def _get_github_token(self) -> Optional[str]:
"""Get GitHub token from Docker secret or secrets file."""
def _get_gitea_token(self) -> Optional[str]:
"""Get GITEA token from Docker secret or secrets file."""
try:
# Try Docker secret first (more secure)
docker_secret_path = Path("/run/secrets/github_token")
@@ -31,17 +31,22 @@ class ProjectService:
if gh_token_path.exists():
return gh_token_path.read_text().strip()
# Try GitHub token from filesystem
# Try GITEA token from filesystem - primary location
gitea_token_path = Path("/home/tony/chorus/business/secrets/gitea-token")
if gitea_token_path.exists():
return gitea_token_path.read_text().strip()
# Try fallback location
gitea_token_fallback = Path("/home/tony/AI/secrets/passwords_and_tokens/gitea-token")
if gitea_token_fallback.exists():
return gitea_token_fallback.read_text().strip()
# Try GitHub token as fallback for external repos
github_token_path = Path("/home/tony/AI/secrets/passwords_and_tokens/github-token")
if github_token_path.exists():
return github_token_path.read_text().strip()
# Fallback to GitLab token if GitHub token doesn't exist
gitlab_token_path = Path("/home/tony/AI/secrets/passwords_and_tokens/claude-gitlab-token")
if gitlab_token_path.exists():
return gitlab_token_path.read_text().strip()
except Exception as e:
print(f"Error reading GitHub token: {e}")
print(f"Error reading GITEA token: {e}")
return None
def get_all_projects(self) -> List[Dict[str, Any]]:
@@ -74,8 +79,8 @@ class ProjectService:
try:
project_id = project_path.name
# Skip if this is the hive project itself
if project_id == 'hive':
# Skip if this is the whoosh project itself
if project_id == 'whoosh':
return None
# Get basic file info
@@ -97,11 +102,11 @@ class ProjectService:
if todos_path.exists():
todos_content = todos_path.read_text(encoding='utf-8')
# Check for GitHub repository
# Check for GITEA repository
git_config_path = project_path / ".git" / "config"
github_repo = None
git_repo = None
if git_config_path.exists():
github_repo = self._extract_github_repo(git_config_path)
git_repo = self._extract_git_repo(git_config_path)
# Determine project status
status = self._determine_project_status(project_path, todos_content)
@@ -121,7 +126,7 @@ class ProjectService:
"created_at": created_at,
"updated_at": updated_at,
"tags": tags,
"github_repo": github_repo,
"git_repo": git_repo,
"workflow_count": workflow_count,
"has_project_plan": project_plan_path.exists(),
"has_todos": todos_path.exists(),
@@ -173,22 +178,29 @@ class ProjectService:
return description[:200] + "..." if len(description) > 200 else description
def _extract_github_repo(self, git_config_path: Path) -> Optional[str]:
"""Extract GitHub repository URL from git config."""
def _extract_git_repo(self, git_config_path: Path) -> Optional[str]:
"""Extract git repository URL from git config (GITEA or GitHub)."""
try:
config_content = git_config_path.read_text()
# Look for GitHub remote URL
# Look for git remote URL (prioritize GITEA)
for line in config_content.split('\n'):
if 'github.com' in line and ('url =' in line or 'url=' in line):
if ('ironwood:3000' in line or 'gitea.' in line) and ('url =' in line or 'url=' in line):
url = line.split('=', 1)[1].strip()
# Extract repo name from URL
# Extract repo name from GITEA URL
if '/ironwood:3000/' in url or '/gitea.' in url:
repo_part = url.split('/')[-2] + '/' + url.split('/')[-1]
if repo_part.endswith('.git'):
repo_part = repo_part[:-4]
return repo_part
elif 'github.com' in line and ('url =' in line or 'url=' in line):
url = line.split('=', 1)[1].strip()
# Extract repo name from GitHub URL (fallback)
if 'github.com/' in url:
repo_part = url.split('github.com/')[-1]
if repo_part.endswith('.git'):
repo_part = repo_part[:-4]
return repo_part
return f"github:{repo_part}" # Mark as external GitHub repo
except Exception:
pass
@@ -213,7 +225,7 @@ class ProjectService:
content_lower = todos_content.lower()
if any(keyword in content_lower for keyword in ['completed', 'done', 'finished']):
if not recent_activity:
return "archived"
return "arcwhooshd"
if any(keyword in content_lower for keyword in ['in progress', 'active', 'working']):
return "active"
@@ -308,19 +320,19 @@ class ProjectService:
if not project_path.exists():
return None
# Get GitHub issues count if repo exists
github_repo = None
# Get git issues count if repo exists
git_repo = None
git_config_path = project_path / ".git" / "config"
if git_config_path.exists():
github_repo = self._extract_github_repo(git_config_path)
git_repo = self._extract_git_repo(git_config_path)
github_issues = 0
github_open_issues = 0
if github_repo and self.github_token:
git_issues = 0
git_open_issues = 0
if git_repo and self.gitea_token:
try:
issues_data = self._get_github_issues(github_repo)
github_issues = len(issues_data)
github_open_issues = len([i for i in issues_data if i['state'] == 'open'])
issues_data = self._get_git_issues(git_repo)
git_issues = len(issues_data)
git_open_issues = len([i for i in issues_data if i['state'] == 'open'])
except Exception:
pass
@@ -359,23 +371,35 @@ class ProjectService:
"active_workflows": max(0, workflow_count - 1) if workflow_count > 0 else 0,
"total_tasks": total_tasks,
"completed_tasks": completed_tasks,
"github_issues": github_issues,
"github_open_issues": github_open_issues,
"git_issues": git_issues,
"git_open_issues": git_open_issues,
"task_completion_rate": completed_tasks / total_tasks if total_tasks > 0 else 0,
"last_activity": last_activity
}
def _get_github_issues(self, repo: str) -> List[Dict]:
"""Fetch GitHub issues for a repository."""
if not self.github_token:
def _get_git_issues(self, repo: str) -> List[Dict]:
"""Fetch git issues for a repository (GITEA or GitHub)."""
if not self.gitea_token:
return []
try:
url = f"{self.github_api_base}/repos/{repo}/issues"
# Determine if this is a GITEA or GitHub repo
if repo.startswith('github:'):
# External GitHub repo
repo = repo[7:] # Remove 'github:' prefix
url = f"https://api.github.com/repos/{repo}/issues"
headers = {
"Authorization": f"token {self.github_token}",
"Authorization": f"token {self.gitea_token}",
"Accept": "application/vnd.github.v3+json"
}
else:
# GITEA repo
url = f"{self.gitea_api_base}/repos/{repo}/issues"
headers = {
"Authorization": f"token {self.gitea_token}",
"Accept": "application/json"
}
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
@@ -461,9 +485,9 @@ class ProjectService:
conn = psycopg2.connect(
host="postgres",
port=5432,
database="hive",
user="hive",
password="hivepass"
database="whoosh",
user="whoosh",
password="whooshpass"
)
print("DEBUG: Database connection successful")
@@ -668,7 +692,7 @@ class ProjectService:
return 'general'
def claim_bzzz_task(self, project_id: str, task_number: int, agent_id: str) -> str:
"""Register task claim with Hive system."""
"""Register task claim with WHOOSH system."""
# For now, just log the claim - in future this would update a database
claim_id = f"{project_id}-{task_number}-{agent_id}"
print(f"Bzzz task claimed: Project {project_id}, Task #{task_number}, Agent {agent_id}")
@@ -679,7 +703,7 @@ class ProjectService:
return claim_id
def update_bzzz_task_status(self, project_id: str, task_number: int, status: str, metadata: Dict[str, Any]) -> None:
"""Update task status in Hive system."""
"""Update task status in WHOOSH system."""
print(f"Bzzz task status update: Project {project_id}, Task #{task_number}, Status: {status}")
print(f"Metadata: {metadata}")
@@ -733,7 +757,7 @@ class ProjectService:
"""Delete a project."""
try:
# For now, projects are filesystem-based and read-only
# This could be extended to archive or remove project directories
# This could be extended to arcwhoosh or remove project directories
project = self.get_project_by_id(project_id)
if not project:
return False