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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user