Initial commit: MCP Hub Community Edition v3.0.0

Community edition generated from private repo via sync pipeline.
Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n,
Supabase, OpenPanel, Appwrite, Directus) with ~587 tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-17 08:34:44 +03:30
commit cf62e65c55
237 changed files with 87596 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
"""
Gitea Plugin
Complete Gitea management through REST API.
"""
from .plugin import GiteaPlugin
__all__ = ["GiteaPlugin"]

451
plugins/gitea/client.py Normal file
View File

@@ -0,0 +1,451 @@
"""
Gitea REST API Client
Handles all HTTP communication with Gitea REST API.
Separates API communication from business logic.
"""
import base64
import logging
from typing import Any
import aiohttp
class GiteaClient:
"""
Gitea REST API client for HTTP communication.
Handles authentication, request formatting, and error handling
for all Gitea API endpoints.
"""
def __init__(self, site_url: str, token: str | None = None, oauth_enabled: bool = False):
"""
Initialize Gitea API client.
Args:
site_url: Gitea instance URL (e.g., https://gitea.example.com)
token: Personal access token for authentication
oauth_enabled: Whether OAuth is enabled for this site
"""
self.site_url = site_url.rstrip("/")
self.api_base = f"{self.site_url}/api/v1"
self.token = token
self.oauth_enabled = oauth_enabled
# Initialize logger
self.logger = logging.getLogger(f"GiteaClient.{site_url}")
def _get_headers(self, additional_headers: dict | None = None) -> dict[str, str]:
"""
Get request headers with authentication.
Args:
additional_headers: Additional headers to include
Returns:
Dict: Headers with authentication
"""
headers = {"Content-Type": "application/json", "accept": "application/json"}
# Add token authentication if available
if self.token:
headers["Authorization"] = f"token {self.token}"
# Merge additional headers
if additional_headers:
headers.update(additional_headers)
return headers
async def request(
self,
method: str,
endpoint: str,
params: dict | None = None,
json_data: dict | None = None,
headers_override: dict | None = None,
) -> Any:
"""
Make authenticated request to Gitea REST API.
Args:
method: HTTP method (GET, POST, PUT, DELETE, PATCH)
endpoint: API endpoint (without base URL)
params: Query parameters
json_data: JSON body data
headers_override: Override default headers
Returns:
API response (dict, list, or None)
Raises:
Exception: On API errors with status code and message
"""
# Build full URL
url = f"{self.api_base}/{endpoint.lstrip('/')}"
# Setup headers
headers = self._get_headers(headers_override)
# Filter out None values from params
if params:
params = {k: v for k, v in params.items() if v is not None}
# Filter None values from JSON data
if json_data:
json_data = {k: v for k, v in json_data.items() if v is not None}
# Make request
self.logger.debug(f"{method} {url}")
self.logger.debug(f"Params: {params}")
self.logger.debug(f"Data: {json_data}")
async with (
aiohttp.ClientSession() as session,
session.request(
method=method, url=url, params=params, json=json_data, headers=headers
) as response,
):
# Log response
self.logger.debug(f"Response status: {response.status}")
# Handle empty responses (e.g., 204 No Content)
if response.status == 204:
return {"success": True, "message": "Operation completed successfully"}
# Try to parse JSON response
try:
response_data = await response.json()
except Exception:
response_text = await response.text()
if response.status >= 400:
raise Exception(f"Gitea API error (status {response.status}): {response_text}")
return {"success": True, "message": response_text}
# Check for errors
if response.status >= 400:
error_msg = response_data.get("message", "Unknown error")
raise Exception(f"Gitea API error (status {response.status}): {error_msg}")
return response_data
# Repository endpoints
async def list_repositories(
self, owner: str | None = None, page: int = 1, limit: int = 30
) -> list[dict]:
"""List repositories for a user/org or current user"""
if owner:
endpoint = f"users/{owner}/repos"
else:
endpoint = "user/repos"
params = {"page": page, "limit": limit}
return await self.request("GET", endpoint, params=params)
async def get_repository(self, owner: str, repo: str) -> dict:
"""Get repository details"""
return await self.request("GET", f"repos/{owner}/{repo}")
async def create_repository(self, data: dict, org: str | None = None) -> dict:
"""Create a new repository"""
if org:
endpoint = f"orgs/{org}/repos"
else:
endpoint = "user/repos"
return await self.request("POST", endpoint, json_data=data)
async def update_repository(self, owner: str, repo: str, data: dict) -> dict:
"""Update repository settings"""
return await self.request("PATCH", f"repos/{owner}/{repo}", json_data=data)
async def delete_repository(self, owner: str, repo: str) -> dict:
"""Delete a repository"""
return await self.request("DELETE", f"repos/{owner}/{repo}")
# Branch endpoints
async def list_branches(
self, owner: str, repo: str, page: int = 1, limit: int = 30
) -> list[dict]:
"""List repository branches"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"repos/{owner}/{repo}/branches", params=params)
async def get_branch(self, owner: str, repo: str, branch: str) -> dict:
"""Get branch details"""
return await self.request("GET", f"repos/{owner}/{repo}/branches/{branch}")
async def create_branch(self, owner: str, repo: str, data: dict) -> dict:
"""Create a new branch"""
return await self.request("POST", f"repos/{owner}/{repo}/branches", json_data=data)
async def delete_branch(self, owner: str, repo: str, branch: str) -> dict:
"""Delete a branch"""
return await self.request("DELETE", f"repos/{owner}/{repo}/branches/{branch}")
# Tag endpoints
async def list_tags(self, owner: str, repo: str, page: int = 1, limit: int = 30) -> list[dict]:
"""List repository tags"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"repos/{owner}/{repo}/tags", params=params)
async def create_tag(self, owner: str, repo: str, data: dict) -> dict:
"""Create a new tag"""
return await self.request("POST", f"repos/{owner}/{repo}/tags", json_data=data)
async def delete_tag(self, owner: str, repo: str, tag: str) -> dict:
"""Delete a tag"""
return await self.request("DELETE", f"repos/{owner}/{repo}/tags/{tag}")
# File endpoints
async def get_file(self, owner: str, repo: str, filepath: str, ref: str | None = None) -> dict:
"""Get file contents"""
params = {"ref": ref} if ref else {}
return await self.request("GET", f"repos/{owner}/{repo}/contents/{filepath}", params=params)
async def create_file(self, owner: str, repo: str, filepath: str, data: dict) -> dict:
"""Create a file"""
# Encode content to base64 if not already encoded
if "content" in data:
content = data["content"]
# Check if content is already base64 encoded
is_already_base64 = data.get("content_is_base64", False)
if not is_already_base64:
# Plain text content - encode to base64
try:
data["content"] = base64.b64encode(content.encode()).decode()
except (AttributeError, UnicodeDecodeError):
# If content is already bytes or has encoding issues, try direct encoding
if isinstance(content, bytes):
data["content"] = base64.b64encode(content).decode()
else:
raise ValueError("Content must be a string or bytes")
# Remove the flag before sending to API
data.pop("content_is_base64", None)
return await self.request(
"POST", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
)
async def update_file(self, owner: str, repo: str, filepath: str, data: dict) -> dict:
"""Update a file"""
# Encode content to base64 if not already encoded
if "content" in data:
content = data["content"]
# Check if content is already base64 encoded
is_already_base64 = data.get("content_is_base64", False)
if not is_already_base64:
# Plain text content - encode to base64
try:
data["content"] = base64.b64encode(content.encode()).decode()
except (AttributeError, UnicodeDecodeError):
# If content is already bytes or has encoding issues, try direct encoding
if isinstance(content, bytes):
data["content"] = base64.b64encode(content).decode()
else:
raise ValueError("Content must be a string or bytes")
# Remove the flag before sending to API
data.pop("content_is_base64", None)
return await self.request(
"PUT", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
)
async def delete_file(
self,
owner: str,
repo: str,
filepath: str,
sha: str,
message: str,
branch: str | None = None,
) -> dict:
"""Delete a file"""
data = {"sha": sha, "message": message}
if branch:
data["branch"] = branch
return await self.request(
"DELETE", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
)
# Issue endpoints
async def list_issues(self, owner: str, repo: str, params: dict) -> list[dict]:
"""List repository issues"""
return await self.request("GET", f"repos/{owner}/{repo}/issues", params=params)
async def get_issue(self, owner: str, repo: str, index: int) -> dict:
"""Get issue details"""
return await self.request("GET", f"repos/{owner}/{repo}/issues/{index}")
async def create_issue(self, owner: str, repo: str, data: dict) -> dict:
"""Create a new issue"""
return await self.request("POST", f"repos/{owner}/{repo}/issues", json_data=data)
async def update_issue(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Update an issue"""
return await self.request("PATCH", f"repos/{owner}/{repo}/issues/{index}", json_data=data)
async def list_issue_comments(self, owner: str, repo: str, index: int) -> list[dict]:
"""List issue comments"""
return await self.request("GET", f"repos/{owner}/{repo}/issues/{index}/comments")
async def create_issue_comment(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Create issue comment"""
return await self.request(
"POST", f"repos/{owner}/{repo}/issues/{index}/comments", json_data=data
)
# Label endpoints
async def list_labels(self, owner: str, repo: str) -> list[dict]:
"""List repository labels"""
return await self.request("GET", f"repos/{owner}/{repo}/labels")
async def create_label(self, owner: str, repo: str, data: dict) -> dict:
"""Create a label"""
return await self.request("POST", f"repos/{owner}/{repo}/labels", json_data=data)
async def delete_label(self, owner: str, repo: str, label_id: int) -> dict:
"""Delete a label"""
return await self.request("DELETE", f"repos/{owner}/{repo}/labels/{label_id}")
# Milestone endpoints
async def list_milestones(self, owner: str, repo: str, state: str | None = None) -> list[dict]:
"""List repository milestones"""
params = {"state": state} if state else {}
return await self.request("GET", f"repos/{owner}/{repo}/milestones", params=params)
async def create_milestone(self, owner: str, repo: str, data: dict) -> dict:
"""Create a milestone"""
return await self.request("POST", f"repos/{owner}/{repo}/milestones", json_data=data)
# Pull Request endpoints
async def list_pull_requests(self, owner: str, repo: str, params: dict) -> list[dict]:
"""List repository pull requests"""
return await self.request("GET", f"repos/{owner}/{repo}/pulls", params=params)
async def get_pull_request(self, owner: str, repo: str, index: int) -> dict:
"""Get pull request details"""
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}")
async def create_pull_request(self, owner: str, repo: str, data: dict) -> dict:
"""Create a new pull request"""
return await self.request("POST", f"repos/{owner}/{repo}/pulls", json_data=data)
async def update_pull_request(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Update a pull request"""
return await self.request("PATCH", f"repos/{owner}/{repo}/pulls/{index}", json_data=data)
async def merge_pull_request(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Merge a pull request"""
return await self.request(
"POST", f"repos/{owner}/{repo}/pulls/{index}/merge", json_data=data
)
async def list_pr_commits(self, owner: str, repo: str, index: int) -> list[dict]:
"""List pull request commits"""
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/commits")
async def list_pr_files(self, owner: str, repo: str, index: int) -> list[dict]:
"""List pull request files"""
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/files")
async def get_pr_diff(self, owner: str, repo: str, index: int) -> str:
"""Get pull request diff"""
# Override accept header for diff
headers = {"accept": "text/plain"}
response = await self.request(
"GET", f"repos/{owner}/{repo}/pulls/{index}.diff", headers_override=headers
)
return response
async def list_pr_reviews(self, owner: str, repo: str, index: int) -> list[dict]:
"""List pull request reviews"""
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/reviews")
async def create_pr_review(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Create pull request review"""
return await self.request(
"POST", f"repos/{owner}/{repo}/pulls/{index}/reviews", json_data=data
)
async def request_pr_reviewers(self, owner: str, repo: str, index: int, data: dict) -> dict:
"""Request pull request reviewers"""
return await self.request(
"POST", f"repos/{owner}/{repo}/pulls/{index}/requested_reviewers", json_data=data
)
# User endpoints
async def get_user(self, username: str) -> dict:
"""Get user information"""
return await self.request("GET", f"users/{username}")
async def list_user_repos(self, username: str, page: int = 1, limit: int = 30) -> list[dict]:
"""List user repositories"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"users/{username}/repos", params=params)
async def search_users(self, query: str | None = None, uid: int | None = None) -> list[dict]:
"""Search users"""
params = {}
if query:
params["q"] = query
if uid:
params["uid"] = uid
response = await self.request("GET", "users/search", params=params)
return response.get("data", [])
# Organization endpoints
async def list_organizations(self, page: int = 1, limit: int = 30) -> list[dict]:
"""List current user's organizations"""
params = {"page": page, "limit": limit}
return await self.request("GET", "user/orgs", params=params)
async def get_organization(self, org: str) -> dict:
"""Get organization information"""
return await self.request("GET", f"orgs/{org}")
async def list_org_repos(self, org: str, page: int = 1, limit: int = 30) -> list[dict]:
"""List organization repositories"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"orgs/{org}/repos", params=params)
async def list_org_teams(self, org: str, page: int = 1, limit: int = 30) -> list[dict]:
"""List organization teams"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"orgs/{org}/teams", params=params)
async def list_team_members(self, team_id: int, page: int = 1, limit: int = 30) -> list[dict]:
"""List team members"""
params = {"page": page, "limit": limit}
return await self.request("GET", f"teams/{team_id}/members", params=params)
# Webhook endpoints
async def list_webhooks(self, owner: str, repo: str) -> list[dict]:
"""List repository webhooks"""
return await self.request("GET", f"repos/{owner}/{repo}/hooks")
async def create_webhook(self, owner: str, repo: str, data: dict) -> dict:
"""Create a webhook"""
return await self.request("POST", f"repos/{owner}/{repo}/hooks", json_data=data)
async def get_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
"""Get webhook details"""
return await self.request("GET", f"repos/{owner}/{repo}/hooks/{hook_id}")
async def update_webhook(self, owner: str, repo: str, hook_id: int, data: dict) -> dict:
"""Update a webhook"""
return await self.request("PATCH", f"repos/{owner}/{repo}/hooks/{hook_id}", json_data=data)
async def delete_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
"""Delete a webhook"""
return await self.request("DELETE", f"repos/{owner}/{repo}/hooks/{hook_id}")
async def test_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
"""Test a webhook"""
return await self.request("POST", f"repos/{owner}/{repo}/hooks/{hook_id}/tests")

View File

@@ -0,0 +1,15 @@
"""
Gitea Plugin Handlers
All tool handlers for Gitea operations.
"""
from . import issues, pull_requests, repositories, users, webhooks
__all__ = [
"repositories",
"issues",
"pull_requests",
"users",
"webhooks",
]

View File

@@ -0,0 +1,516 @@
"""Issue Handler - manages Gitea issues, labels, milestones, and comments"""
import json
from typing import Any
from plugins.gitea.client import GiteaClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === ISSUES ===
{
"name": "list_issues",
"method_name": "list_issues",
"description": "List issues in a Gitea repository with filters. Returns paginated list of issues.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"state": {
"type": "string",
"description": "Filter by state",
"enum": ["open", "closed", "all"],
"default": "open",
},
"labels": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated label IDs",
},
"q": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search query",
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "get_issue",
"method_name": "get_issue",
"description": "Get details of a specific issue by number.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
},
"required": ["owner", "repo", "issue_number"],
},
"scope": "read",
},
{
"name": "create_issue",
"method_name": "create_issue",
"description": "Create a new issue in a Gitea repository with optional labels, assignees, and milestone.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"title": {
"type": "string",
"description": "Issue title",
"minLength": 1,
"maxLength": 255,
},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Issue description (supports Markdown)",
},
"assignee": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Assignee username",
},
"assignees": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "List of assignee usernames",
},
"labels": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "List of label IDs",
},
"milestone": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Milestone ID",
},
"closed": {
"type": "boolean",
"description": "Create as closed",
"default": False,
},
},
"required": ["owner", "repo", "title"],
},
"scope": "write",
},
{
"name": "update_issue",
"method_name": "update_issue",
"description": "Update an existing issue. Can modify title, body, state, assignees, labels, and milestone.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Issue title",
},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Issue description",
},
"state": {
"anyOf": [{"type": "string", "enum": ["open", "closed"]}, {"type": "null"}],
"description": "Issue state",
},
"assignee": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Assignee username",
},
"assignees": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "List of assignee usernames",
},
"labels": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "List of label IDs",
},
"milestone": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Milestone ID",
},
},
"required": ["owner", "repo", "issue_number"],
},
"scope": "write",
},
{
"name": "close_issue",
"method_name": "close_issue",
"description": "Close an issue.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
},
"required": ["owner", "repo", "issue_number"],
},
"scope": "write",
},
{
"name": "reopen_issue",
"method_name": "reopen_issue",
"description": "Reopen a closed issue.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
},
"required": ["owner", "repo", "issue_number"],
},
"scope": "write",
},
# === COMMENTS ===
{
"name": "list_issue_comments",
"method_name": "list_issue_comments",
"description": "List all comments on an issue.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
},
"required": ["owner", "repo", "issue_number"],
},
"scope": "read",
},
{
"name": "create_issue_comment",
"method_name": "create_issue_comment",
"description": "Add a comment to an issue.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"issue_number": {
"type": "integer",
"description": "Issue number",
"minimum": 1,
},
"body": {
"type": "string",
"description": "Comment body (supports Markdown)",
"minLength": 1,
},
},
"required": ["owner", "repo", "issue_number", "body"],
},
"scope": "write",
},
# === LABELS ===
{
"name": "list_labels",
"method_name": "list_labels",
"description": "List all labels in a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "create_label",
"method_name": "create_label",
"description": "Create a new label in a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"name": {
"type": "string",
"description": "Label name",
"minLength": 1,
"maxLength": 50,
},
"color": {
"type": "string",
"description": "Label color (hex without #, e.g., 'ff0000')",
"pattern": "^[0-9A-Fa-f]{6}$",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Label description",
"maxLength": 200,
},
},
"required": ["owner", "repo", "name", "color"],
},
"scope": "write",
},
# === MILESTONES ===
{
"name": "list_milestones",
"method_name": "list_milestones",
"description": "List all milestones in a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"state": {
"anyOf": [
{"type": "string", "enum": ["open", "closed", "all"]},
{"type": "null"},
],
"description": "Filter by state",
"default": "open",
},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "create_milestone",
"method_name": "create_milestone",
"description": "Create a new milestone in a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"title": {
"type": "string",
"description": "Milestone title",
"minLength": 1,
"maxLength": 255,
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Milestone description",
},
"due_on": {
"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}],
"description": "Due date (ISO 8601 format)",
},
"state": {
"type": "string",
"description": "State",
"enum": ["open", "closed"],
"default": "open",
},
},
"required": ["owner", "repo", "title"],
},
"scope": "write",
},
]
async def list_issues(
client: GiteaClient,
owner: str,
repo: str,
state: str = "open",
labels: str | None = None,
q: str | None = None,
page: int = 1,
limit: int = 30,
) -> str:
"""List repository issues"""
params = {"state": state, "labels": labels, "q": q, "page": page, "limit": limit}
issues = await client.list_issues(owner, repo, params)
result = {"success": True, "count": len(issues), "issues": issues}
return json.dumps(result, indent=2)
async def get_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
"""Get issue details"""
issue = await client.get_issue(owner, repo, issue_number)
result = {"success": True, "issue": issue}
return json.dumps(result, indent=2)
async def create_issue(
client: GiteaClient,
owner: str,
repo: str,
title: str,
body: str | None = None,
assignee: str | None = None,
assignees: list[str] | None = None,
labels: list[int] | None = None,
milestone: int | None = None,
closed: bool = False,
) -> str:
"""Create a new issue"""
data = {
"title": title,
"body": body,
"assignee": assignee,
"assignees": assignees,
"labels": labels,
"milestone": milestone,
"closed": closed,
}
issue = await client.create_issue(owner, repo, data)
result = {
"success": True,
"message": f"Issue #{issue['number']} created successfully",
"issue": issue,
}
return json.dumps(result, indent=2)
async def update_issue(
client: GiteaClient, owner: str, repo: str, issue_number: int, **kwargs
) -> str:
"""Update an issue"""
# Build update data from kwargs
data = {
k: v
for k, v in kwargs.items()
if v is not None and k not in ["owner", "repo", "issue_number"]
}
issue = await client.update_issue(owner, repo, issue_number, data)
result = {
"success": True,
"message": f"Issue #{issue_number} updated successfully",
"issue": issue,
}
return json.dumps(result, indent=2)
async def close_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
"""Close an issue"""
data = {"state": "closed"}
issue = await client.update_issue(owner, repo, issue_number, data)
result = {
"success": True,
"message": f"Issue #{issue_number} closed successfully",
"issue": issue,
}
return json.dumps(result, indent=2)
async def reopen_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
"""Reopen an issue"""
data = {"state": "open"}
issue = await client.update_issue(owner, repo, issue_number, data)
result = {
"success": True,
"message": f"Issue #{issue_number} reopened successfully",
"issue": issue,
}
return json.dumps(result, indent=2)
# Comment operations
async def list_issue_comments(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
"""List issue comments"""
comments = await client.list_issue_comments(owner, repo, issue_number)
result = {"success": True, "count": len(comments), "comments": comments}
return json.dumps(result, indent=2)
async def create_issue_comment(
client: GiteaClient, owner: str, repo: str, issue_number: int, body: str
) -> str:
"""Create issue comment"""
data = {"body": body}
comment = await client.create_issue_comment(owner, repo, issue_number, data)
result = {
"success": True,
"message": f"Comment added to issue #{issue_number}",
"comment": comment,
}
return json.dumps(result, indent=2)
# Label operations
async def list_labels(client: GiteaClient, owner: str, repo: str) -> str:
"""List repository labels"""
labels = await client.list_labels(owner, repo)
result = {"success": True, "count": len(labels), "labels": labels}
return json.dumps(result, indent=2)
async def create_label(
client: GiteaClient,
owner: str,
repo: str,
name: str,
color: str,
description: str | None = None,
) -> str:
"""Create a label"""
data = {"name": name, "color": color, "description": description}
label = await client.create_label(owner, repo, data)
result = {"success": True, "message": f"Label '{name}' created successfully", "label": label}
return json.dumps(result, indent=2)
# Milestone operations
async def list_milestones(
client: GiteaClient, owner: str, repo: str, state: str | None = None
) -> str:
"""List repository milestones"""
milestones = await client.list_milestones(owner, repo, state=state)
result = {"success": True, "count": len(milestones), "milestones": milestones}
return json.dumps(result, indent=2)
async def create_milestone(
client: GiteaClient,
owner: str,
repo: str,
title: str,
description: str | None = None,
due_on: str | None = None,
state: str = "open",
) -> str:
"""Create a milestone"""
data = {"title": title, "description": description, "due_on": due_on, "state": state}
milestone = await client.create_milestone(owner, repo, data)
result = {
"success": True,
"message": f"Milestone '{title}' created successfully",
"milestone": milestone,
}
return json.dumps(result, indent=2)

View File

@@ -0,0 +1,629 @@
"""Pull Request Handler - manages Gitea pull requests, reviews, and merges"""
import json
from typing import Any
from plugins.gitea.client import GiteaClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === PULL REQUESTS ===
{
"name": "list_pull_requests",
"method_name": "list_pull_requests",
"description": "List pull requests in a Gitea repository with filters.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"state": {
"type": "string",
"description": "Filter by state",
"enum": ["open", "closed", "all"],
"default": "open",
},
"sort": {
"type": "string",
"description": "Sort by",
"enum": ["created", "updated", "comments", "recentupdate"],
"default": "created",
},
"labels": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated label IDs",
},
"milestone": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Milestone name",
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "get_pull_request",
"method_name": "get_pull_request",
"description": "Get details of a specific pull request by number.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "create_pull_request",
"method_name": "create_pull_request",
"description": "Create a new pull request in a Gitea repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"title": {
"type": "string",
"description": "Pull request title",
"minLength": 1,
"maxLength": 255,
},
"head": {
"type": "string",
"description": "Source branch (head branch)",
"minLength": 1,
},
"base": {
"type": "string",
"description": "Target branch (base branch)",
"minLength": 1,
},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Pull request description (supports Markdown)",
},
"assignee": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Assignee username",
},
"assignees": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "List of assignee usernames",
},
"labels": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "List of label IDs",
},
"milestone": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Milestone ID",
},
},
"required": ["owner", "repo", "title", "head", "base"],
},
"scope": "write",
},
{
"name": "update_pull_request",
"method_name": "update_pull_request",
"description": "Update an existing pull request. Can modify title, body, state, assignees, labels, and milestone.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Pull request title",
},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Pull request description",
},
"state": {
"anyOf": [{"type": "string", "enum": ["open", "closed"]}, {"type": "null"}],
"description": "Pull request state",
},
"assignee": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Assignee username",
},
"assignees": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "List of assignee usernames",
},
"labels": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "List of label IDs",
},
"milestone": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Milestone ID",
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "write",
},
{
"name": "merge_pull_request",
"method_name": "merge_pull_request",
"description": "Merge a pull request using specified merge method.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
"method": {
"type": "string",
"description": "Merge method",
"enum": ["merge", "rebase", "rebase-merge", "squash"],
"default": "merge",
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Merge commit title",
},
"message": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Merge commit message",
},
"delete_branch_after_merge": {
"type": "boolean",
"description": "Delete source branch after merge",
"default": False,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "write",
},
{
"name": "close_pull_request",
"method_name": "close_pull_request",
"description": "Close a pull request without merging.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "write",
},
{
"name": "reopen_pull_request",
"method_name": "reopen_pull_request",
"description": "Reopen a closed pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "write",
},
# === PR DETAILS ===
{
"name": "list_pr_commits",
"method_name": "list_pr_commits",
"description": "List all commits in a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "list_pr_files",
"method_name": "list_pr_files",
"description": "List all changed files in a pull request with additions/deletions count.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "get_pr_diff",
"method_name": "get_pr_diff",
"description": "Get the unified diff of a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "list_pr_comments",
"method_name": "list_pr_comments",
"description": "List all comments on a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "create_pr_comment",
"method_name": "create_pr_comment",
"description": "Add a comment to a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
"body": {
"type": "string",
"description": "Comment body (supports Markdown)",
"minLength": 1,
},
},
"required": ["owner", "repo", "pr_number", "body"],
},
"scope": "write",
},
# === PR REVIEWS ===
{
"name": "list_pr_reviews",
"method_name": "list_pr_reviews",
"description": "List all reviews on a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
},
"required": ["owner", "repo", "pr_number"],
},
"scope": "read",
},
{
"name": "create_pr_review",
"method_name": "create_pr_review",
"description": "Create a review for a pull request (approve, request changes, or comment).",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
"event": {
"type": "string",
"description": "Review event type",
"enum": ["APPROVED", "REQUEST_CHANGES", "COMMENT"],
},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Review comment (optional for APPROVED)",
},
},
"required": ["owner", "repo", "pr_number", "event"],
},
"scope": "write",
},
{
"name": "request_pr_reviewers",
"method_name": "request_pr_reviewers",
"description": "Request reviewers for a pull request.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"pr_number": {
"type": "integer",
"description": "Pull request number",
"minimum": 1,
},
"reviewers": {
"type": "array",
"items": {"type": "string"},
"description": "List of reviewer usernames",
"minItems": 1,
},
},
"required": ["owner", "repo", "pr_number", "reviewers"],
},
"scope": "write",
},
]
async def list_pull_requests(
client: GiteaClient,
owner: str,
repo: str,
state: str = "open",
sort: str = "created",
labels: str | None = None,
milestone: str | None = None,
page: int = 1,
limit: int = 30,
) -> str:
"""List repository pull requests"""
params = {
"state": state,
"sort": sort,
"labels": labels,
"milestone": milestone,
"page": page,
"limit": limit,
}
prs = await client.list_pull_requests(owner, repo, params)
result = {"success": True, "count": len(prs), "pull_requests": prs}
return json.dumps(result, indent=2)
async def get_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""Get pull request details"""
pr = await client.get_pull_request(owner, repo, pr_number)
result = {"success": True, "pull_request": pr}
return json.dumps(result, indent=2)
async def create_pull_request(
client: GiteaClient,
owner: str,
repo: str,
title: str,
head: str,
base: str,
body: str | None = None,
assignee: str | None = None,
assignees: list[str] | None = None,
labels: list[int] | None = None,
milestone: int | None = None,
) -> str:
"""Create a new pull request"""
data = {
"title": title,
"head": head,
"base": base,
"body": body,
"assignee": assignee,
"assignees": assignees,
"labels": labels,
"milestone": milestone,
}
pr = await client.create_pull_request(owner, repo, data)
result = {
"success": True,
"message": f"Pull request #{pr['number']} created successfully",
"pull_request": pr,
}
return json.dumps(result, indent=2)
async def update_pull_request(
client: GiteaClient, owner: str, repo: str, pr_number: int, **kwargs
) -> str:
"""Update a pull request"""
# Build update data from kwargs
data = {
k: v for k, v in kwargs.items() if v is not None and k not in ["owner", "repo", "pr_number"]
}
pr = await client.update_pull_request(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Pull request #{pr_number} updated successfully",
"pull_request": pr,
}
return json.dumps(result, indent=2)
async def merge_pull_request(
client: GiteaClient,
owner: str,
repo: str,
pr_number: int,
method: str = "merge",
title: str | None = None,
message: str | None = None,
delete_branch_after_merge: bool = False,
) -> str:
"""Merge a pull request"""
data = {
"Do": method,
"MergeTitleField": title,
"MergeMessageField": message,
"delete_branch_after_merge": delete_branch_after_merge,
}
merge_result = await client.merge_pull_request(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Pull request #{pr_number} merged successfully using {method}",
"result": merge_result,
}
return json.dumps(result, indent=2)
async def close_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""Close a pull request"""
data = {"state": "closed"}
pr = await client.update_pull_request(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Pull request #{pr_number} closed successfully",
"pull_request": pr,
}
return json.dumps(result, indent=2)
async def reopen_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""Reopen a pull request"""
data = {"state": "open"}
pr = await client.update_pull_request(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Pull request #{pr_number} reopened successfully",
"pull_request": pr,
}
return json.dumps(result, indent=2)
# PR Details
async def list_pr_commits(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""List pull request commits"""
commits = await client.list_pr_commits(owner, repo, pr_number)
result = {"success": True, "count": len(commits), "commits": commits}
return json.dumps(result, indent=2)
async def list_pr_files(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""List pull request files"""
files = await client.list_pr_files(owner, repo, pr_number)
result = {"success": True, "count": len(files), "files": files}
return json.dumps(result, indent=2)
async def get_pr_diff(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""Get pull request diff"""
diff = await client.get_pr_diff(owner, repo, pr_number)
result = {"success": True, "diff": diff}
return json.dumps(result, indent=2)
async def list_pr_comments(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""List pull request comments"""
# PR comments are same as issue comments in Gitea API
comments = await client.list_issue_comments(owner, repo, pr_number)
result = {"success": True, "count": len(comments), "comments": comments}
return json.dumps(result, indent=2)
async def create_pr_comment(
client: GiteaClient, owner: str, repo: str, pr_number: int, body: str
) -> str:
"""Create pull request comment"""
data = {"body": body}
comment = await client.create_issue_comment(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Comment added to pull request #{pr_number}",
"comment": comment,
}
return json.dumps(result, indent=2)
# PR Reviews
async def list_pr_reviews(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
"""List pull request reviews"""
reviews = await client.list_pr_reviews(owner, repo, pr_number)
result = {"success": True, "count": len(reviews), "reviews": reviews}
return json.dumps(result, indent=2)
async def create_pr_review(
client: GiteaClient, owner: str, repo: str, pr_number: int, event: str, body: str | None = None
) -> str:
"""Create pull request review"""
data = {"event": event, "body": body}
review = await client.create_pr_review(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Review {event} added to pull request #{pr_number}",
"review": review,
}
return json.dumps(result, indent=2)
async def request_pr_reviewers(
client: GiteaClient, owner: str, repo: str, pr_number: int, reviewers: list[str]
) -> str:
"""Request pull request reviewers"""
data = {"reviewers": reviewers}
reviewers_result = await client.request_pr_reviewers(owner, repo, pr_number, data)
result = {
"success": True,
"message": f"Reviewers requested for pull request #{pr_number}",
"result": reviewers_result,
}
return json.dumps(result, indent=2)

View File

@@ -0,0 +1,709 @@
"""Repository Handler - manages Gitea repositories, branches, tags, and files"""
import json
from typing import Any
from plugins.gitea.client import GiteaClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === REPOSITORIES ===
{
"name": "list_repositories",
"method_name": "list_repositories",
"description": "List Gitea repositories for a user/organization or current user. Returns repository list with metadata.",
"schema": {
"type": "object",
"properties": {
"owner": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Repository owner username/org (null for current user repos)",
},
"type": {
"type": "string",
"description": "Filter by repository type",
"enum": ["all", "owner", "collaborative", "member"],
"default": "all",
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
},
"scope": "read",
},
{
"name": "get_repository",
"method_name": "get_repository",
"description": "Get details of a specific Gitea repository. Returns complete repository information.",
"schema": {
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner username/org",
"minLength": 1,
},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "create_repository",
"method_name": "create_repository",
"description": "Create a new Gitea repository. Can create user or organization repository with optional auto-initialization.",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Repository name",
"minLength": 1,
"maxLength": 100,
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Repository description",
"maxLength": 500,
},
"private": {
"type": "boolean",
"description": "Make repository private",
"default": False,
},
"auto_init": {
"type": "boolean",
"description": "Initialize with README",
"default": False,
},
"gitignores": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Gitignore template name",
},
"license": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "License template name",
},
"readme": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Readme template",
},
"default_branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Default branch name",
},
"org": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Create under organization (null for user repo)",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "update_repository",
"method_name": "update_repository",
"description": "Update Gitea repository settings like name, description, visibility, and features.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New repository name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Repository description",
},
"website": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Repository website",
},
"private": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Make repository private",
},
"archived": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Archive repository",
},
"has_issues": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable issues",
},
"has_wiki": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable wiki",
},
"default_branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Default branch",
},
"allow_merge_commits": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Allow merge commits",
},
"allow_rebase": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Allow rebase",
},
"allow_squash_merge": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Allow squash merge",
},
},
"required": ["owner", "repo"],
},
"scope": "write",
},
{
"name": "delete_repository",
"method_name": "delete_repository",
"description": "Delete a Gitea repository permanently. This action cannot be undone!",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
},
"required": ["owner", "repo"],
},
"scope": "admin",
},
# === BRANCHES ===
{
"name": "list_branches",
"method_name": "list_branches",
"description": "List all branches in a Gitea repository with commit information and protection status.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "get_branch",
"method_name": "get_branch",
"description": "Get details of a specific branch including latest commit and protection settings.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"branch": {"type": "string", "description": "Branch name", "minLength": 1},
},
"required": ["owner", "repo", "branch"],
},
"scope": "read",
},
{
"name": "create_branch",
"method_name": "create_branch",
"description": "Create a new branch in a Gitea repository from existing branch or commit.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"new_branch_name": {
"type": "string",
"description": "New branch name",
"minLength": 1,
},
"old_branch_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Source branch (default: default branch)",
},
"old_ref_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Source commit SHA or ref",
},
},
"required": ["owner", "repo", "new_branch_name"],
},
"scope": "write",
},
{
"name": "delete_branch",
"method_name": "delete_branch",
"description": "Delete a branch from a Gitea repository. Cannot delete default branch.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"branch": {
"type": "string",
"description": "Branch name to delete",
"minLength": 1,
},
},
"required": ["owner", "repo", "branch"],
},
"scope": "write",
},
# === TAGS ===
{
"name": "list_tags",
"method_name": "list_tags",
"description": "List all tags in a Gitea repository with commit information.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "create_tag",
"method_name": "create_tag",
"description": "Create a new tag in a Gitea repository at specific commit.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"tag_name": {"type": "string", "description": "Tag name", "minLength": 1},
"message": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Tag message (annotated tag)",
},
"target": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Target commit SHA (default: latest)",
},
},
"required": ["owner", "repo", "tag_name"],
},
"scope": "write",
},
{
"name": "delete_tag",
"method_name": "delete_tag",
"description": "Delete a tag from a Gitea repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"tag": {"type": "string", "description": "Tag name to delete", "minLength": 1},
},
"required": ["owner", "repo", "tag"],
},
"scope": "write",
},
# === FILES ===
{
"name": "get_file",
"method_name": "get_file",
"description": "Get file contents from a Gitea repository. Returns file content (Base64 encoded) and metadata.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"path": {
"type": "string",
"description": "File path in repository",
"minLength": 1,
},
"ref": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Branch/tag/commit (default: default branch)",
},
},
"required": ["owner", "repo", "path"],
},
"scope": "read",
},
{
"name": "create_file",
"method_name": "create_file",
"description": "Create a new file in a Gitea repository with commit message.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"path": {
"type": "string",
"description": "File path to create",
"minLength": 1,
},
"content": {
"type": "string",
"description": "File content (will be Base64 encoded automatically unless content_is_base64=true)",
},
"message": {"type": "string", "description": "Commit message", "minLength": 1},
"branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Target branch (default: default branch)",
},
"new_branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Create new branch for this commit",
},
"author_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author name",
},
"author_email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author email",
},
"content_is_base64": {
"type": "boolean",
"description": "Set to true if content is already Base64 encoded (skip automatic encoding)",
"default": False,
},
},
"required": ["owner", "repo", "path", "content", "message"],
},
"scope": "write",
},
{
"name": "update_file",
"method_name": "update_file",
"description": "Update an existing file in a Gitea repository. Requires current file SHA for conflict detection.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"path": {
"type": "string",
"description": "File path to update",
"minLength": 1,
},
"content": {
"type": "string",
"description": "New file content (will be Base64 encoded automatically)",
},
"sha": {
"type": "string",
"description": "Current file SHA (for conflict detection)",
"minLength": 1,
},
"message": {"type": "string", "description": "Commit message", "minLength": 1},
"branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Target branch (default: default branch)",
},
"new_branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Create new branch for this commit",
},
"content_is_base64": {
"type": "boolean",
"description": "Set to true if content is already Base64 encoded (skip automatic encoding)",
"default": False,
},
},
"required": ["owner", "repo", "path", "content", "sha", "message"],
},
"scope": "write",
},
{
"name": "delete_file",
"method_name": "delete_file",
"description": "Delete a file from a Gitea repository. Requires current file SHA for conflict detection.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"path": {
"type": "string",
"description": "File path to delete",
"minLength": 1,
},
"sha": {
"type": "string",
"description": "Current file SHA (for conflict detection)",
"minLength": 1,
},
"message": {"type": "string", "description": "Commit message", "minLength": 1},
"branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Target branch (default: default branch)",
},
},
"required": ["owner", "repo", "path", "sha", "message"],
},
"scope": "write",
},
]
async def list_repositories(
client: GiteaClient, owner: str | None = None, type: str = "all", page: int = 1, limit: int = 30
) -> str:
"""List Gitea repositories"""
repos = await client.list_repositories(owner=owner, page=page, limit=limit)
result = {"success": True, "count": len(repos), "repositories": repos}
return json.dumps(result, indent=2)
async def get_repository(client: GiteaClient, owner: str, repo: str) -> str:
"""Get repository details"""
repository = await client.get_repository(owner, repo)
result = {"success": True, "repository": repository}
return json.dumps(result, indent=2)
async def create_repository(
client: GiteaClient,
name: str,
description: str | None = None,
private: bool = False,
auto_init: bool = False,
gitignores: str | None = None,
license: str | None = None,
readme: str | None = None,
default_branch: str | None = None,
org: str | None = None,
) -> str:
"""Create a new repository"""
data = {
"name": name,
"description": description,
"private": private,
"auto_init": auto_init,
"gitignores": gitignores,
"license": license,
"readme": readme,
"default_branch": default_branch,
}
repository = await client.create_repository(data, org=org)
result = {
"success": True,
"message": f"Repository '{name}' created successfully",
"repository": repository,
}
return json.dumps(result, indent=2)
async def update_repository(client: GiteaClient, owner: str, repo: str, **kwargs) -> str:
"""Update repository settings"""
# Build update data from kwargs
data = {k: v for k, v in kwargs.items() if v is not None and k not in ["owner", "repo"]}
repository = await client.update_repository(owner, repo, data)
result = {
"success": True,
"message": f"Repository '{owner}/{repo}' updated successfully",
"repository": repository,
}
return json.dumps(result, indent=2)
async def delete_repository(client: GiteaClient, owner: str, repo: str) -> str:
"""Delete a repository"""
await client.delete_repository(owner, repo)
result = {"success": True, "message": f"Repository '{owner}/{repo}' deleted successfully"}
return json.dumps(result, indent=2)
# Branch operations
async def list_branches(
client: GiteaClient, owner: str, repo: str, page: int = 1, limit: int = 30
) -> str:
"""List repository branches"""
branches = await client.list_branches(owner, repo, page=page, limit=limit)
result = {"success": True, "count": len(branches), "branches": branches}
return json.dumps(result, indent=2)
async def get_branch(client: GiteaClient, owner: str, repo: str, branch: str) -> str:
"""Get branch details"""
branch_info = await client.get_branch(owner, repo, branch)
result = {"success": True, "branch": branch_info}
return json.dumps(result, indent=2)
async def create_branch(
client: GiteaClient,
owner: str,
repo: str,
new_branch_name: str,
old_branch_name: str | None = None,
old_ref_name: str | None = None,
) -> str:
"""Create a new branch"""
data = {
"new_branch_name": new_branch_name,
"old_branch_name": old_branch_name,
"old_ref_name": old_ref_name,
}
branch = await client.create_branch(owner, repo, data)
result = {
"success": True,
"message": f"Branch '{new_branch_name}' created successfully",
"branch": branch,
}
return json.dumps(result, indent=2)
async def delete_branch(client: GiteaClient, owner: str, repo: str, branch: str) -> str:
"""Delete a branch"""
await client.delete_branch(owner, repo, branch)
result = {"success": True, "message": f"Branch '{branch}' deleted successfully"}
return json.dumps(result, indent=2)
# Tag operations
async def list_tags(
client: GiteaClient, owner: str, repo: str, page: int = 1, limit: int = 30
) -> str:
"""List repository tags"""
tags = await client.list_tags(owner, repo, page=page, limit=limit)
result = {"success": True, "count": len(tags), "tags": tags}
return json.dumps(result, indent=2)
async def create_tag(
client: GiteaClient,
owner: str,
repo: str,
tag_name: str,
message: str | None = None,
target: str | None = None,
) -> str:
"""Create a new tag"""
data = {"tag_name": tag_name, "message": message, "target": target}
tag = await client.create_tag(owner, repo, data)
result = {"success": True, "message": f"Tag '{tag_name}' created successfully", "tag": tag}
return json.dumps(result, indent=2)
async def delete_tag(client: GiteaClient, owner: str, repo: str, tag: str) -> str:
"""Delete a tag"""
await client.delete_tag(owner, repo, tag)
result = {"success": True, "message": f"Tag '{tag}' deleted successfully"}
return json.dumps(result, indent=2)
# File operations
async def get_file(
client: GiteaClient, owner: str, repo: str, path: str, ref: str | None = None
) -> str:
"""Get file contents"""
file_data = await client.get_file(owner, repo, path, ref=ref)
result = {"success": True, "file": file_data}
return json.dumps(result, indent=2)
async def create_file(
client: GiteaClient,
owner: str,
repo: str,
path: str,
content: str,
message: str,
branch: str | None = None,
new_branch: str | None = None,
author_name: str | None = None,
author_email: str | None = None,
content_is_base64: bool = False,
) -> str:
"""Create a new file"""
data = {
"content": content,
"message": message,
"branch": branch,
"new_branch": new_branch,
"content_is_base64": content_is_base64,
"author": {},
}
if author_name:
data["author"]["name"] = author_name
if author_email:
data["author"]["email"] = author_email
file_result = await client.create_file(owner, repo, path, data)
result = {
"success": True,
"message": f"File '{path}' created successfully",
"file": file_result,
}
return json.dumps(result, indent=2)
async def update_file(
client: GiteaClient,
owner: str,
repo: str,
path: str,
content: str,
sha: str,
message: str,
branch: str | None = None,
new_branch: str | None = None,
content_is_base64: bool = False,
) -> str:
"""Update an existing file"""
data = {
"content": content,
"sha": sha,
"message": message,
"branch": branch,
"new_branch": new_branch,
"content_is_base64": content_is_base64,
}
file_result = await client.update_file(owner, repo, path, data)
result = {
"success": True,
"message": f"File '{path}' updated successfully",
"file": file_result,
}
return json.dumps(result, indent=2)
async def delete_file(
client: GiteaClient,
owner: str,
repo: str,
path: str,
sha: str,
message: str,
branch: str | None = None,
) -> str:
"""Delete a file from repository"""
await client.delete_file(owner, repo, path, sha, message, branch)
result = {"success": True, "message": f"File '{path}' deleted successfully"}
return json.dumps(result, indent=2)

View File

@@ -0,0 +1,245 @@
"""User & Organization Handler - manages Gitea users, organizations, and teams"""
import json
from typing import Any
from plugins.gitea.client import GiteaClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === USERS ===
{
"name": "get_user",
"method_name": "get_user",
"description": "Get information about a Gitea user by username.",
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Username to look up",
"minLength": 1,
}
},
"required": ["username"],
},
"scope": "read",
},
{
"name": "list_user_repos",
"method_name": "list_user_repos",
"description": "List all public repositories of a Gitea user.",
"schema": {
"type": "object",
"properties": {
"username": {"type": "string", "description": "Username", "minLength": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["username"],
},
"scope": "read",
},
{
"name": "search_users",
"method_name": "search_users",
"description": "Search for Gitea users by query or user ID.",
"schema": {
"type": "object",
"properties": {
"q": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search query (username or full name)",
},
"uid": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "User ID to search for",
},
},
},
"scope": "read",
},
# === ORGANIZATIONS ===
{
"name": "list_organizations",
"method_name": "list_organizations",
"description": "List organizations for the current authenticated user.",
"schema": {
"type": "object",
"properties": {
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
},
"scope": "read",
},
{
"name": "get_organization",
"method_name": "get_organization",
"description": "Get information about a Gitea organization by name.",
"schema": {
"type": "object",
"properties": {
"org": {"type": "string", "description": "Organization name", "minLength": 1}
},
"required": ["org"],
},
"scope": "read",
},
{
"name": "list_org_repos",
"method_name": "list_org_repos",
"description": "List all repositories of an organization.",
"schema": {
"type": "object",
"properties": {
"org": {"type": "string", "description": "Organization name", "minLength": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["org"],
},
"scope": "read",
},
# === TEAMS ===
{
"name": "list_org_teams",
"method_name": "list_org_teams",
"description": "List all teams in an organization.",
"schema": {
"type": "object",
"properties": {
"org": {"type": "string", "description": "Organization name", "minLength": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["org"],
},
"scope": "read",
},
{
"name": "list_team_members",
"method_name": "list_team_members",
"description": "List all members of a team.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "integer", "description": "Team ID", "minimum": 1},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"limit": {
"type": "integer",
"description": "Items per page (1-100)",
"default": 30,
"minimum": 1,
"maximum": 100,
},
},
"required": ["team_id"],
},
"scope": "read",
},
]
async def get_user(client: GiteaClient, username: str) -> str:
"""Get user information"""
user = await client.get_user(username)
result = {"success": True, "user": user}
return json.dumps(result, indent=2)
async def list_user_repos(
client: GiteaClient, username: str, page: int = 1, limit: int = 30
) -> str:
"""List user repositories"""
repos = await client.list_user_repos(username, page=page, limit=limit)
result = {"success": True, "count": len(repos), "repositories": repos}
return json.dumps(result, indent=2)
async def search_users(client: GiteaClient, q: str | None = None, uid: int | None = None) -> str:
"""Search users"""
users = await client.search_users(query=q, uid=uid)
result = {"success": True, "count": len(users), "users": users}
return json.dumps(result, indent=2)
# Organization operations
async def list_organizations(client: GiteaClient, page: int = 1, limit: int = 30) -> str:
"""List organizations"""
orgs = await client.list_organizations(page=page, limit=limit)
result = {"success": True, "count": len(orgs), "organizations": orgs}
return json.dumps(result, indent=2)
async def get_organization(client: GiteaClient, org: str) -> str:
"""Get organization information"""
organization = await client.get_organization(org)
result = {"success": True, "organization": organization}
return json.dumps(result, indent=2)
async def list_org_repos(client: GiteaClient, org: str, page: int = 1, limit: int = 30) -> str:
"""List organization repositories"""
repos = await client.list_org_repos(org, page=page, limit=limit)
result = {"success": True, "count": len(repos), "repositories": repos}
return json.dumps(result, indent=2)
# Team operations
async def list_org_teams(client: GiteaClient, org: str, page: int = 1, limit: int = 30) -> str:
"""List organization teams"""
teams = await client.list_org_teams(org, page=page, limit=limit)
result = {"success": True, "count": len(teams), "teams": teams}
return json.dumps(result, indent=2)
async def list_team_members(
client: GiteaClient, team_id: int, page: int = 1, limit: int = 30
) -> str:
"""List team members"""
members = await client.list_team_members(team_id, page=page, limit=limit)
result = {"success": True, "count": len(members), "members": members}
return json.dumps(result, indent=2)

View File

@@ -0,0 +1,216 @@
"""Webhook Handler - manages Gitea webhooks for repositories"""
import json
from typing import Any
from plugins.gitea.client import GiteaClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === WEBHOOKS ===
{
"name": "list_webhooks",
"method_name": "list_webhooks",
"description": "List all webhooks configured for a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
},
"required": ["owner", "repo"],
},
"scope": "read",
},
{
"name": "create_webhook",
"method_name": "create_webhook",
"description": "Create a new webhook for a repository to receive event notifications.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"url": {
"type": "string",
"description": "Webhook URL to send events to",
"format": "uri",
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"create",
"delete",
"fork",
"push",
"issues",
"issue_assign",
"issue_label",
"issue_milestone",
"issue_comment",
"pull_request",
"pull_request_assign",
"pull_request_label",
"pull_request_milestone",
"pull_request_comment",
"pull_request_review_approved",
"pull_request_review_rejected",
"pull_request_review_comment",
"pull_request_sync",
"wiki",
"repository",
"release",
],
},
"description": "List of events to trigger webhook",
"minItems": 1,
},
"content_type": {
"type": "string",
"description": "Content type for webhook payload",
"enum": ["json", "form"],
"default": "json",
},
"secret": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Secret for webhook signature verification",
},
"active": {
"type": "boolean",
"description": "Activate webhook immediately",
"default": True,
},
"type": {
"type": "string",
"description": "Webhook type",
"enum": [
"gitea",
"gogs",
"slack",
"discord",
"dingtalk",
"telegram",
"msteams",
"feishu",
"wechatwork",
"packagist",
],
"default": "gitea",
},
},
"required": ["owner", "repo", "url", "events"],
},
"scope": "admin",
},
{
"name": "delete_webhook",
"method_name": "delete_webhook",
"description": "Delete a webhook from a repository.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"webhook_id": {
"type": "integer",
"description": "Webhook ID to delete",
"minimum": 1,
},
},
"required": ["owner", "repo", "webhook_id"],
},
"scope": "admin",
},
{
"name": "test_webhook",
"method_name": "test_webhook",
"description": "Send a test payload to a webhook to verify it's working.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"webhook_id": {
"type": "integer",
"description": "Webhook ID to test",
"minimum": 1,
},
},
"required": ["owner", "repo", "webhook_id"],
},
"scope": "admin",
},
{
"name": "get_webhook",
"method_name": "get_webhook",
"description": "Get details of a specific webhook.",
"schema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
"webhook_id": {"type": "integer", "description": "Webhook ID", "minimum": 1},
},
"required": ["owner", "repo", "webhook_id"],
},
"scope": "read",
},
]
async def list_webhooks(client: GiteaClient, owner: str, repo: str) -> str:
"""List repository webhooks"""
webhooks = await client.list_webhooks(owner, repo)
result = {"success": True, "count": len(webhooks), "webhooks": webhooks}
return json.dumps(result, indent=2)
async def create_webhook(
client: GiteaClient,
owner: str,
repo: str,
url: str,
events: list[str],
content_type: str = "json",
secret: str | None = None,
active: bool = True,
type: str = "gitea",
) -> str:
"""Create a webhook"""
# Build webhook configuration
config = {"url": url, "content_type": content_type}
if secret:
config["secret"] = secret
data = {"type": type, "config": config, "events": events, "active": active}
webhook = await client.create_webhook(owner, repo, data)
result = {
"success": True,
"message": f"Webhook created successfully for {owner}/{repo}",
"webhook": webhook,
}
return json.dumps(result, indent=2)
async def delete_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
"""Delete a webhook"""
await client.delete_webhook(owner, repo, webhook_id)
result = {"success": True, "message": f"Webhook {webhook_id} deleted successfully"}
return json.dumps(result, indent=2)
async def test_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
"""Test a webhook"""
test_result = await client.test_webhook(owner, repo, webhook_id)
result = {
"success": True,
"message": f"Test payload sent to webhook {webhook_id}",
"result": test_result,
}
return json.dumps(result, indent=2)
async def get_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
"""Get webhook details"""
webhook = await client.get_webhook(owner, repo, webhook_id)
result = {"success": True, "webhook": webhook}
return json.dumps(result, indent=2)

161
plugins/gitea/plugin.py Normal file
View File

@@ -0,0 +1,161 @@
"""
Gitea Plugin - Clean Architecture
Complete Gitea management through REST API with OAuth support.
Modular handlers for better organization and maintainability.
"""
from typing import Any
from plugins.base import BasePlugin
from plugins.gitea import handlers
from plugins.gitea.client import GiteaClient
class GiteaPlugin(BasePlugin):
"""
Gitea project plugin - Clean architecture.
Provides comprehensive Gitea management capabilities including:
- Repository management (CRUD, branches, tags, files)
- Issue tracking (issues, labels, milestones, comments)
- Pull requests (PRs, reviews, merges)
- User and organization management
- Webhook configuration
- OAuth integration
"""
@staticmethod
def get_plugin_name() -> str:
"""Return plugin type identifier"""
return "gitea"
@staticmethod
def get_required_config_keys() -> list[str]:
"""Return required configuration keys"""
return ["url"] # Token is optional (can use OAuth)
def __init__(self, config: dict[str, Any], project_id: str | None = None):
"""
Initialize Gitea plugin with handlers.
Args:
config: Configuration dictionary containing:
- url: Gitea instance URL
- token: (Optional) Personal access token
- oauth_enabled: (Optional) Whether OAuth is enabled
project_id: Optional project ID (auto-generated if not provided)
"""
super().__init__(config, project_id=project_id)
# Create Gitea API client
self.client = GiteaClient(
site_url=config["url"],
token=config.get("token"),
oauth_enabled=config.get("oauth_enabled", False),
)
# No handler instances needed in Option B architecture
# Tools are delegated directly to handler functions
@staticmethod
def get_tool_specifications() -> list[dict[str, Any]]:
"""
Return all tool specifications for ToolGenerator.
This method is called by ToolGenerator to create unified tools
with site parameter routing.
Returns:
List of tool specification dictionaries
"""
specs = []
# Collect specifications from all handlers
specs.extend(handlers.repositories.get_tool_specifications())
specs.extend(handlers.issues.get_tool_specifications())
specs.extend(handlers.pull_requests.get_tool_specifications())
specs.extend(handlers.users.get_tool_specifications())
specs.extend(handlers.webhooks.get_tool_specifications())
return specs
def __getattr__(self, name: str):
"""
Dynamically delegate method calls to appropriate handlers.
This allows ToolGenerator to call methods like plugin.list_repositories()
without explicitly defining each method.
Args:
name: Method name being called
Returns:
Handler function from the appropriate handler module
"""
# Try to find the method in handlers modules
try:
# Check repositories handlers
if hasattr(handlers.repositories, name):
func = getattr(handlers.repositories, name)
# Create wrapper that passes self.client
async def wrapper(**kwargs):
return await func(self.client, **kwargs)
return wrapper
# Check issues handlers
if hasattr(handlers.issues, name):
func = getattr(handlers.issues, name)
async def wrapper(**kwargs):
return await func(self.client, **kwargs)
return wrapper
# Check pull_requests handlers
if hasattr(handlers.pull_requests, name):
func = getattr(handlers.pull_requests, name)
async def wrapper(**kwargs):
return await func(self.client, **kwargs)
return wrapper
# Check users handlers
if hasattr(handlers.users, name):
func = getattr(handlers.users, name)
async def wrapper(**kwargs):
return await func(self.client, **kwargs)
return wrapper
# Check webhooks handlers
if hasattr(handlers.webhooks, name):
func = getattr(handlers.webhooks, name)
async def wrapper(**kwargs):
return await func(self.client, **kwargs)
return wrapper
# Method not found in any handler
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
except AttributeError:
raise
async def health_check(self) -> dict[str, Any]:
"""
Check if Gitea instance is accessible.
Returns:
Dict containing health check result
"""
try:
# Try to get user information (requires authentication)
await self.client.request("GET", "user")
return {"healthy": True, "message": "Gitea instance is accessible"}
except Exception as e:
return {"healthy": False, "message": f"Gitea health check failed: {str(e)}"}

View File

@@ -0,0 +1,127 @@
"""
Gitea Plugin Pydantic Schemas
All validation schemas for Gitea plugin operations.
"""
from .common import (
ErrorResponse,
GiteaPermissions,
GiteaTimestamps,
GiteaUser,
PaginationParams,
Site,
SuccessResponse,
)
from .issue import (
Comment,
CreateCommentRequest,
CreateIssueRequest,
CreateLabelRequest,
CreateMilestoneRequest,
Issue,
IssueListFilters,
Label,
Milestone,
UpdateIssueRequest,
)
from .pull_request import (
CreatePullRequestRequest,
CreateReviewRequest,
MergePullRequestRequest,
PRBranchInfo,
PRCommit,
PRFile,
PRListFilters,
PRReview,
PullRequest,
RequestReviewersRequest,
UpdatePullRequestRequest,
)
from .repository import (
Branch,
CreateBranchRequest,
CreateFileRequest,
CreateRepositoryRequest,
CreateTagRequest,
FileContent,
Repository,
Tag,
UpdateFileRequest,
UpdateRepositoryRequest,
)
from .user import (
Email,
Organization,
SearchOrgsRequest,
SearchUsersRequest,
Team,
TeamMember,
User,
)
from .webhook import (
CreateWebhookRequest,
UpdateWebhookRequest,
Webhook,
WebhookConfig,
WebhookTestResult,
)
__all__ = [
# Common
"Site",
"PaginationParams",
"ErrorResponse",
"SuccessResponse",
"GiteaUser",
"GiteaPermissions",
"GiteaTimestamps",
# Repository
"Repository",
"Branch",
"Tag",
"FileContent",
"CreateRepositoryRequest",
"UpdateRepositoryRequest",
"CreateBranchRequest",
"CreateTagRequest",
"CreateFileRequest",
"UpdateFileRequest",
# Issue
"Label",
"Milestone",
"Issue",
"Comment",
"CreateIssueRequest",
"UpdateIssueRequest",
"CreateCommentRequest",
"CreateLabelRequest",
"CreateMilestoneRequest",
"IssueListFilters",
# Pull Request
"PRBranchInfo",
"PullRequest",
"PRReview",
"PRCommit",
"PRFile",
"CreatePullRequestRequest",
"UpdatePullRequestRequest",
"MergePullRequestRequest",
"CreateReviewRequest",
"RequestReviewersRequest",
"PRListFilters",
# User
"User",
"Organization",
"Team",
"TeamMember",
"Email",
"SearchUsersRequest",
"SearchOrgsRequest",
# Webhook
"Webhook",
"WebhookConfig",
"CreateWebhookRequest",
"UpdateWebhookRequest",
"WebhookTestResult",
]

View File

@@ -0,0 +1,73 @@
"""
Common Pydantic Schemas for Gitea Plugin
Shared validation schemas used across Gitea handlers.
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class Site(BaseModel):
"""Gitea site configuration"""
model_config = ConfigDict(extra="forbid")
site_id: str = Field(..., description="Site identifier (e.g., 'site1')")
url: str = Field(..., description="Gitea instance URL")
token: str | None = Field(None, description="Personal access token")
alias: str | None = Field(None, description="Site alias")
oauth_enabled: bool = Field(default=False, description="Whether OAuth is enabled")
class PaginationParams(BaseModel):
"""Pagination parameters for list endpoints"""
model_config = ConfigDict(extra="forbid")
page: int = Field(default=1, ge=1, description="Page number (starts at 1)")
limit: int = Field(default=30, ge=1, le=100, description="Number of items per page (1-100)")
class ErrorResponse(BaseModel):
"""Standard error response"""
error: bool = Field(default=True)
message: str = Field(..., description="Error message")
code: str | None = Field(None, description="Error code")
details: dict[str, Any] | None = Field(None, description="Additional error details")
class SuccessResponse(BaseModel):
"""Standard success response"""
success: bool = Field(default=True)
message: str = Field(..., description="Success message")
data: dict[str, Any] | None = Field(None, description="Response data")
class GiteaUser(BaseModel):
"""Gitea user information"""
model_config = ConfigDict(extra="allow")
id: int
login: str
full_name: str | None = None
email: str | None = None
avatar_url: str | None = None
is_admin: bool = False
class GiteaPermissions(BaseModel):
"""Repository permissions"""
model_config = ConfigDict(extra="allow")
admin: bool = False
push: bool = False
pull: bool = False
class GiteaTimestamps(BaseModel):
"""Common timestamp fields"""
model_config = ConfigDict(extra="allow")
created_at: datetime | None = None
updated_at: datetime | None = None

View File

@@ -0,0 +1,184 @@
"""
Issue Pydantic Schemas
Validation schemas for Gitea issue operations.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator
from .common import GiteaUser
class Label(BaseModel):
"""Issue/PR label"""
model_config = ConfigDict(extra="allow")
id: int
name: str
color: str
description: str | None = None
url: str | None = None
class Milestone(BaseModel):
"""Issue/PR milestone"""
model_config = ConfigDict(extra="allow")
id: int
title: str
description: str | None = None
state: str # "open" or "closed"
open_issues: int = 0
closed_issues: int = 0
created_at: datetime
updated_at: datetime | None = None
closed_at: datetime | None = None
due_on: datetime | None = None
class Issue(BaseModel):
"""Gitea issue model"""
model_config = ConfigDict(extra="allow")
id: int
number: int
user: GiteaUser
original_author: str | None = None
original_author_id: int | None = None
title: str
body: str | None = None
ref: str | None = None
labels: list[Label] = []
milestone: Milestone | None = None
assignee: GiteaUser | None = None
assignees: list[GiteaUser] = []
state: str # "open" or "closed"
is_locked: bool = False
comments: int = 0
created_at: datetime
updated_at: datetime
closed_at: datetime | None = None
due_date: datetime | None = None
pull_request: dict | None = None # Non-null if this is a PR
repository: dict | None = None
html_url: str
url: str
class Comment(BaseModel):
"""Issue/PR comment"""
model_config = ConfigDict(extra="allow")
id: int
html_url: str
pull_request_url: str | None = None
issue_url: str | None = None
user: GiteaUser
original_author: str | None = None
original_author_id: int | None = None
body: str
created_at: datetime
updated_at: datetime
class CreateIssueRequest(BaseModel):
"""Request to create an issue"""
model_config = ConfigDict(extra="forbid")
title: str = Field(..., min_length=1, max_length=255, description="Issue title")
body: str | None = Field(None, description="Issue body/description")
assignee: str | None = Field(None, description="Username of assignee")
assignees: list[str] | None = Field(None, description="List of assignee usernames")
closed: bool | None = Field(False, description="Create as closed")
due_date: datetime | None = Field(None, description="Due date")
labels: list[int] | None = Field(None, description="List of label IDs")
milestone: int | None = Field(None, description="Milestone ID")
ref: str | None = Field(None, description="Issue ref")
class UpdateIssueRequest(BaseModel):
"""Request to update an issue"""
model_config = ConfigDict(extra="forbid")
title: str | None = Field(None, min_length=1, max_length=255, description="Issue title")
body: str | None = Field(None, description="Issue body/description")
assignee: str | None = Field(None, description="Username of assignee")
assignees: list[str] | None = Field(None, description="List of assignee usernames")
state: str | None = Field(None, description="State (open/closed)")
due_date: datetime | None = Field(None, description="Due date")
labels: list[int] | None = Field(None, description="List of label IDs")
milestone: int | None = Field(None, description="Milestone ID")
ref: str | None = Field(None, description="Issue ref")
unset_due_date: bool | None = Field(None, description="Unset due date")
@classmethod
@field_validator("state")
def validate_state(cls, v):
if v and v not in ["open", "closed"]:
raise ValueError("State must be 'open' or 'closed'")
return v
class CreateCommentRequest(BaseModel):
"""Request to create a comment"""
model_config = ConfigDict(extra="forbid")
body: str = Field(..., min_length=1, description="Comment body")
class CreateLabelRequest(BaseModel):
"""Request to create a label"""
model_config = ConfigDict(extra="forbid")
name: str = Field(..., min_length=1, max_length=50, description="Label name")
color: str = Field(..., pattern=r"^[0-9A-Fa-f]{6}$", description="Label color (hex without #)")
description: str | None = Field(None, max_length=200, description="Label description")
class CreateMilestoneRequest(BaseModel):
"""Request to create a milestone"""
model_config = ConfigDict(extra="forbid")
title: str = Field(..., min_length=1, max_length=255, description="Milestone title")
description: str | None = Field(None, description="Milestone description")
due_on: datetime | None = Field(None, description="Due date")
state: str | None = Field("open", description="State (open/closed)")
@classmethod
@field_validator("state")
def validate_state(cls, v):
if v not in ["open", "closed"]:
raise ValueError("State must be 'open' or 'closed'")
return v
class IssueListFilters(BaseModel):
"""Filters for listing issues"""
model_config = ConfigDict(extra="forbid")
state: str | None = Field("open", description="Filter by state (open/closed/all)")
labels: str | None = Field(None, description="Comma-separated label IDs")
q: str | None = Field(None, description="Search query")
type: str | None = Field(None, description="Filter by type (issues/pulls)")
milestones: str | None = Field(None, description="Comma-separated milestone names")
since: datetime | None = Field(None, description="Only show items updated after this time")
before: datetime | None = Field(None, description="Only show items updated before this time")
created_by: str | None = Field(None, description="Filter by creator username")
assigned_by: str | None = Field(None, description="Filter by assignee username")
mentioned_by: str | None = Field(None, description="Filter by mentioned username")
@classmethod
@field_validator("state")
def validate_state(cls, v):
if v and v not in ["open", "closed", "all"]:
raise ValueError("State must be 'open', 'closed', or 'all'")
return v
@classmethod
@field_validator("type")
def validate_type(cls, v):
if v and v not in ["issues", "pulls"]:
raise ValueError("Type must be 'issues' or 'pulls'")
return v

View File

@@ -0,0 +1,215 @@
"""
Pull Request Pydantic Schemas
Validation schemas for Gitea pull request operations.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator
from .common import GiteaUser
from .issue import Label, Milestone
class PRBranchInfo(BaseModel):
"""Pull request branch information"""
model_config = ConfigDict(extra="allow")
label: str
ref: str
sha: str
repo_id: int
repository: dict | None = None
class PullRequest(BaseModel):
"""Gitea pull request model"""
model_config = ConfigDict(extra="allow")
id: int
number: int
user: GiteaUser
title: str
body: str | None = None
labels: list[Label] = []
milestone: Milestone | None = None
assignee: GiteaUser | None = None
assignees: list[GiteaUser] = []
requested_reviewers: list[GiteaUser] = []
state: str # "open" or "closed"
is_locked: bool = False
comments: int = 0
html_url: str
diff_url: str
patch_url: str
mergeable: bool = False
merged: bool = False
merged_at: datetime | None = None
merge_commit_sha: str | None = None
merged_by: GiteaUser | None = None
base: PRBranchInfo
head: PRBranchInfo
merge_base: str | None = None
due_date: datetime | None = None
created_at: datetime
updated_at: datetime
closed_at: datetime | None = None
allow_maintainer_edit: bool = False
url: str
class PRReview(BaseModel):
"""Pull request review"""
model_config = ConfigDict(extra="allow")
id: int
user: GiteaUser
body: str | None = None
commit_id: str
state: str # "PENDING", "APPROVED", "REQUEST_CHANGES", "COMMENT"
html_url: str
pull_request_url: str
submitted_at: datetime
class PRCommit(BaseModel):
"""Pull request commit"""
model_config = ConfigDict(extra="allow")
sha: str
commit: dict
url: str
html_url: str
comments_url: str
author: GiteaUser | None = None
committer: GiteaUser | None = None
parents: list[dict] = []
class PRFile(BaseModel):
"""Pull request file change"""
model_config = ConfigDict(extra="allow")
filename: str
status: str # "added", "removed", "modified", "renamed"
additions: int
deletions: int
changes: int
raw_url: str | None = None
contents_url: str | None = None
patch: str | None = None
previous_filename: str | None = None
class CreatePullRequestRequest(BaseModel):
"""Request to create a pull request"""
model_config = ConfigDict(extra="forbid")
title: str = Field(..., min_length=1, max_length=255, description="PR title")
head: str = Field(..., description="Source branch")
base: str = Field(..., description="Target branch")
body: str | None = Field(None, description="PR description")
assignee: str | None = Field(None, description="Username of assignee")
assignees: list[str] | None = Field(None, description="List of assignee usernames")
labels: list[int] | None = Field(None, description="List of label IDs")
milestone: int | None = Field(None, description="Milestone ID")
due_date: datetime | None = Field(None, description="Due date")
class UpdatePullRequestRequest(BaseModel):
"""Request to update a pull request"""
model_config = ConfigDict(extra="forbid")
title: str | None = Field(None, min_length=1, max_length=255, description="PR title")
body: str | None = Field(None, description="PR description")
assignee: str | None = Field(None, description="Username of assignee")
assignees: list[str] | None = Field(None, description="List of assignee usernames")
labels: list[int] | None = Field(None, description="List of label IDs")
milestone: int | None = Field(None, description="Milestone ID")
state: str | None = Field(None, description="State (open/closed)")
due_date: datetime | None = Field(None, description="Due date")
unset_due_date: bool | None = Field(None, description="Unset due date")
allow_maintainer_edit: bool | None = Field(None, description="Allow maintainer edit")
@classmethod
@field_validator("state")
def validate_state(cls, v):
if v and v not in ["open", "closed"]:
raise ValueError("State must be 'open' or 'closed'")
return v
class MergePullRequestRequest(BaseModel):
"""Request to merge a pull request"""
model_config = ConfigDict(extra="forbid")
Do: str = Field(
..., description="Merge method (merge/rebase/rebase-merge/squash/manually-merged)"
)
MergeTitleField: str | None = Field(None, description="Merge commit title")
MergeMessageField: str | None = Field(None, description="Merge commit message")
delete_branch_after_merge: bool | None = Field(False, description="Delete branch after merge")
force_merge: bool | None = Field(False, description="Force merge even if not mergeable")
head_commit_id: str | None = Field(None, description="Expected HEAD commit SHA")
merge_when_checks_succeed: bool | None = Field(False, description="Auto-merge when checks pass")
@classmethod
@field_validator("Do")
def validate_merge_method(cls, v):
allowed = ["merge", "rebase", "rebase-merge", "squash", "manually-merged"]
if v not in allowed:
raise ValueError(f"Merge method must be one of: {', '.join(allowed)}")
return v
class CreateReviewRequest(BaseModel):
"""Request to create a review"""
model_config = ConfigDict(extra="forbid")
body: str | None = Field(None, description="Review comment")
event: str = Field(..., description="Review event (APPROVED/REQUEST_CHANGES/COMMENT)")
comments: list[dict] | None = Field(None, description="Inline review comments")
@classmethod
@field_validator("event")
def validate_event(cls, v):
allowed = ["APPROVED", "REQUEST_CHANGES", "COMMENT"]
if v not in allowed:
raise ValueError(f"Review event must be one of: {', '.join(allowed)}")
return v
class RequestReviewersRequest(BaseModel):
"""Request reviewers for a pull request"""
model_config = ConfigDict(extra="forbid")
reviewers: list[str] = Field(..., min_items=1, description="List of reviewer usernames")
team_reviewers: list[str] | None = Field(None, description="List of team slugs")
class PRListFilters(BaseModel):
"""Filters for listing pull requests"""
model_config = ConfigDict(extra="forbid")
state: str | None = Field("open", description="Filter by state (open/closed/all)")
sort: str | None = Field(
"created", description="Sort by (created/updated/comments/recentupdate)"
)
labels: str | None = Field(None, description="Comma-separated label IDs")
milestone: str | None = Field(None, description="Milestone name")
@classmethod
@field_validator("state")
def validate_state(cls, v):
if v and v not in ["open", "closed", "all"]:
raise ValueError("State must be 'open', 'closed', or 'all'")
return v
@classmethod
@field_validator("sort")
def validate_sort(cls, v):
allowed = ["created", "updated", "comments", "recentupdate"]
if v and v not in allowed:
raise ValueError(f"Sort must be one of: {', '.join(allowed)}")
return v

View File

@@ -0,0 +1,196 @@
"""
Repository Pydantic Schemas
Validation schemas for Gitea repository operations.
"""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
from .common import GiteaPermissions, GiteaUser
class Repository(BaseModel):
"""Gitea repository model"""
model_config = ConfigDict(extra="allow")
id: int
owner: GiteaUser
name: str
full_name: str
description: str | None = None
empty: bool = False
private: bool = False
fork: bool = False
template: bool = False
parent: Optional["Repository"] = None
mirror: bool = False
size: int = 0
language: str | None = None
languages_url: str | None = None
html_url: str
ssh_url: str
clone_url: str
original_url: str | None = None
website: str | None = None
stars_count: int = 0
forks_count: int = 0
watchers_count: int = 0
open_issues_count: int = 0
open_pr_counter: int = 0
release_counter: int = 0
default_branch: str = "main"
archived: bool = False
created_at: datetime
updated_at: datetime
permissions: GiteaPermissions | None = None
has_issues: bool = True
internal_tracker: dict | None = None
has_wiki: bool = False
has_pull_requests: bool = True
has_projects: bool = False
ignore_whitespace_conflicts: bool = False
allow_merge_commits: bool = True
allow_rebase: bool = True
allow_rebase_explicit: bool = True
allow_squash_merge: bool = True
default_merge_style: str = "merge"
avatar_url: str | None = None
class Branch(BaseModel):
"""Git branch model"""
model_config = ConfigDict(extra="allow")
name: str
commit: dict
protected: bool = False
required_approvals: int | None = None
enable_status_check: bool = False
status_check_contexts: list[str] | None = None
user_can_push: bool = False
user_can_merge: bool = False
class Tag(BaseModel):
"""Git tag model"""
model_config = ConfigDict(extra="allow")
name: str
message: str | None = None
id: str
commit: dict
zipball_url: str | None = None
tarball_url: str | None = None
class FileContent(BaseModel):
"""File content model"""
model_config = ConfigDict(extra="allow")
name: str
path: str
sha: str
size: int
url: str
html_url: str
git_url: str
download_url: str | None = None
type: str # "file" or "dir"
content: str | None = None # Base64 encoded
encoding: str | None = None
target: str | None = None # For symlinks
submodule_git_url: str | None = None
class CreateRepositoryRequest(BaseModel):
"""Request to create a repository"""
model_config = ConfigDict(extra="forbid")
name: str = Field(..., min_length=1, max_length=100, description="Repository name")
description: str | None = Field(None, max_length=500, description="Repository description")
private: bool = Field(default=False, description="Make repository private")
auto_init: bool = Field(default=False, description="Initialize with README")
gitignores: str | None = Field(None, description="Gitignore template")
license: str | None = Field(None, description="License template")
readme: str | None = Field(None, description="Readme template")
default_branch: str | None = Field(None, description="Default branch name")
trust_model: str | None = Field(
None, description="Trust model (default, collaborator, committer, collaboratorcommitter)"
)
@classmethod
@field_validator("name")
def validate_name(cls, v):
"""Validate repository name"""
if not v.replace("-", "").replace("_", "").replace(".", "").isalnum():
raise ValueError(
"Repository name can only contain alphanumeric characters, hyphens, underscores, and dots"
)
return v
class UpdateRepositoryRequest(BaseModel):
"""Request to update a repository"""
model_config = ConfigDict(extra="forbid")
name: str | None = Field(None, min_length=1, max_length=100, description="New repository name")
description: str | None = Field(None, max_length=500, description="Repository description")
website: str | None = Field(None, description="Repository website")
private: bool | None = Field(None, description="Make repository private")
archived: bool | None = Field(None, description="Archive repository")
has_issues: bool | None = Field(None, description="Enable issues")
has_wiki: bool | None = Field(None, description="Enable wiki")
default_branch: str | None = Field(None, description="Default branch")
allow_merge_commits: bool | None = Field(None, description="Allow merge commits")
allow_rebase: bool | None = Field(None, description="Allow rebase")
allow_squash_merge: bool | None = Field(None, description="Allow squash merge")
class CreateBranchRequest(BaseModel):
"""Request to create a branch"""
model_config = ConfigDict(extra="forbid")
new_branch_name: str = Field(..., min_length=1, description="New branch name")
old_branch_name: str | None = Field(None, description="Source branch (default: default branch)")
old_ref_name: str | None = Field(None, description="Source commit SHA or ref")
class CreateTagRequest(BaseModel):
"""Request to create a tag"""
model_config = ConfigDict(extra="forbid")
tag_name: str = Field(..., min_length=1, description="Tag name")
message: str | None = Field(None, description="Tag message")
target: str | None = Field(None, description="Target commit SHA (default: latest)")
class CreateFileRequest(BaseModel):
"""Request to create a file"""
model_config = ConfigDict(extra="forbid")
content: str = Field(..., description="File content (will be Base64 encoded)")
message: str = Field(..., min_length=1, description="Commit message")
branch: str | None = Field(None, description="Branch name (default: default branch)")
author_name: str | None = Field(None, description="Author name")
author_email: str | None = Field(None, description="Author email")
committer_name: str | None = Field(None, description="Committer name")
committer_email: str | None = Field(None, description="Committer email")
new_branch: str | None = Field(None, description="Create new branch for this commit")
class UpdateFileRequest(BaseModel):
"""Request to update a file"""
model_config = ConfigDict(extra="forbid")
content: str = Field(..., description="New file content (will be Base64 encoded)")
message: str = Field(..., min_length=1, description="Commit message")
sha: str = Field(..., description="SHA of the file being replaced")
branch: str | None = Field(None, description="Branch name (default: default branch)")
author_name: str | None = Field(None, description="Author name")
author_email: str | None = Field(None, description="Author email")
committer_name: str | None = Field(None, description="Committer name")
committer_email: str | None = Field(None, description="Committer email")
new_branch: str | None = Field(None, description="Create new branch for this commit")

View File

@@ -0,0 +1,98 @@
"""
User & Organization Pydantic Schemas
Validation schemas for Gitea user and organization operations.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class User(BaseModel):
"""Gitea user model"""
model_config = ConfigDict(extra="allow")
id: int
login: str
full_name: str | None = None
email: str | None = None
avatar_url: str
language: str | None = None
is_admin: bool = False
last_login: datetime | None = None
created: datetime
restricted: bool = False
active: bool = True
prohibit_login: bool = False
location: str | None = None
website: str | None = None
description: str | None = None
visibility: str | None = None
followers_count: int = 0
following_count: int = 0
starred_repos_count: int = 0
username: str | None = None # Alias for login
class Organization(BaseModel):
"""Gitea organization model"""
model_config = ConfigDict(extra="allow")
id: int
name: str # org username
full_name: str | None = None
avatar_url: str
description: str | None = None
website: str | None = None
location: str | None = None
visibility: str # "public", "limited", "private"
repo_admin_change_team_access: bool = False
username: str | None = None # Alias for name
class Team(BaseModel):
"""Organization team model"""
model_config = ConfigDict(extra="allow")
id: int
name: str
description: str | None = None
organization: Organization | None = None
permission: str # "none", "read", "write", "admin", "owner"
can_create_org_repo: bool = False
includes_all_repositories: bool = False
units: list[str] | None = None
units_map: dict | None = None
class TeamMember(BaseModel):
"""Team member model"""
model_config = ConfigDict(extra="allow")
user: User
role: str | None = None # Role in team
class Email(BaseModel):
"""User email model"""
model_config = ConfigDict(extra="allow")
email: str
verified: bool
primary: bool
class SearchUsersRequest(BaseModel):
"""Request to search users"""
model_config = ConfigDict(extra="forbid")
q: str | None = Field(None, description="Search query")
uid: int | None = Field(None, description="User ID")
class SearchOrgsRequest(BaseModel):
"""Request to search organizations"""
model_config = ConfigDict(extra="forbid")
q: str | None = Field(None, description="Search query")

View File

@@ -0,0 +1,163 @@
"""
Webhook Pydantic Schemas
Validation schemas for Gitea webhook operations.
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, field_validator
class Webhook(BaseModel):
"""Gitea webhook model"""
model_config = ConfigDict(extra="allow")
id: int
type: str # "gitea", "gogs", "slack", "discord", etc.
config: dict[str, Any]
events: list[str]
active: bool
updated_at: datetime
created_at: datetime
class WebhookConfig(BaseModel):
"""Webhook configuration"""
model_config = ConfigDict(extra="allow")
url: str
content_type: str = "json" # "json" or "form"
secret: str | None = None
http_method: str | None = "POST"
branch_filter: str | None = None
class CreateWebhookRequest(BaseModel):
"""Request to create a webhook"""
model_config = ConfigDict(extra="forbid")
type: str = Field(
default="gitea",
description="Webhook type (gitea/gogs/slack/discord/dingtalk/telegram/msteams/feishu/wechatwork/packagist)",
)
config: dict[str, Any] = Field(..., description="Webhook configuration")
events: list[str] = Field(..., min_items=1, description="List of events to trigger webhook")
active: bool = Field(default=True, description="Activate webhook immediately")
branch_filter: str | None = Field(None, description="Branch filter pattern")
@classmethod
@field_validator("type")
def validate_type(cls, v):
allowed = [
"gitea",
"gogs",
"slack",
"discord",
"dingtalk",
"telegram",
"msteams",
"feishu",
"wechatwork",
"packagist",
]
if v not in allowed:
raise ValueError(f"Webhook type must be one of: {', '.join(allowed)}")
return v
@classmethod
@field_validator("events")
def validate_events(cls, v):
allowed_events = [
"create",
"delete",
"fork",
"push",
"issues",
"issue_assign",
"issue_label",
"issue_milestone",
"issue_comment",
"pull_request",
"pull_request_assign",
"pull_request_label",
"pull_request_milestone",
"pull_request_comment",
"pull_request_review_approved",
"pull_request_review_rejected",
"pull_request_review_comment",
"pull_request_sync",
"wiki",
"repository",
"release",
]
for event in v:
if event not in allowed_events:
raise ValueError(
f"Invalid event: {event}. Must be one of: {', '.join(allowed_events)}"
)
return v
@classmethod
@field_validator("config")
def validate_config(cls, v):
"""Validate webhook config has required fields"""
if "url" not in v:
raise ValueError("Webhook config must contain 'url'")
if "content_type" not in v:
v["content_type"] = "json"
return v
class UpdateWebhookRequest(BaseModel):
"""Request to update a webhook"""
model_config = ConfigDict(extra="forbid")
config: dict[str, Any] | None = Field(None, description="Webhook configuration")
events: list[str] | None = Field(None, description="List of events to trigger webhook")
active: bool | None = Field(None, description="Activate/deactivate webhook")
branch_filter: str | None = Field(None, description="Branch filter pattern")
@classmethod
@field_validator("events")
def validate_events(cls, v):
if v is None:
return v
allowed_events = [
"create",
"delete",
"fork",
"push",
"issues",
"issue_assign",
"issue_label",
"issue_milestone",
"issue_comment",
"pull_request",
"pull_request_assign",
"pull_request_label",
"pull_request_milestone",
"pull_request_comment",
"pull_request_review_approved",
"pull_request_review_rejected",
"pull_request_review_comment",
"pull_request_sync",
"wiki",
"repository",
"release",
]
for event in v:
if event not in allowed_events:
raise ValueError(f"Invalid event: {event}")
return v
class WebhookTestResult(BaseModel):
"""Webhook test result"""
model_config = ConfigDict(extra="allow")
success: bool
message: str | None = None
response_code: int | None = None
response_body: str | None = None