Update gitea/admin.py

This commit is contained in:
2026-01-13 19:08:17 +00:00
parent 673e5d434d
commit d96c439c06

View File

@@ -1,10 +1,18 @@
"""
title: Gitea Admin
author: Jeff Smith + minimax + Claude
version: 1.2.0
version: 1.3.0
license: MIT
description: Gitea organization management - teams, members, repository settings, and admin operations
requirements: pydantic, httpx
changelog:
1.3.0:
- Fixed create_org() to use correct /orgs endpoint instead of /admin/users
- Added configurable SSL verification (VERIFY_SSL valve)
- Improved error handling with JSON message parsing
- Added repository visibility parameter support
- Corrected admin-only operation documentation
- Enhanced error messages with better context
"""
from typing import Optional, Callable, Any, List, Dict
@@ -28,6 +36,10 @@ class Tools:
default=True,
description="Allow users to override defaults via UserValves",
)
VERIFY_SSL: bool = Field(
default=True,
description="Verify SSL certificates (disable only for self-signed certs)",
)
class UserValves(BaseModel):
"""Per-user configuration"""
@@ -88,6 +100,15 @@ class Tools:
raise ValueError(f"Repository must be in owner/repo format, got: {repo}")
return repo.split("/", 1)
def _format_error(self, e: httpx.HTTPStatusError) -> str:
"""Format HTTP error with JSON message if available."""
try:
error_json = e.response.json()
error_msg = error_json.get("message", e.response.text[:200])
except:
error_msg = e.response.text[:200]
return f"HTTP Error {e.response.status_code}: {error_msg}"
async def _get_team_id(
self,
team_name: str,
@@ -105,7 +126,9 @@ class Tools:
return None
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/orgs/{org}/teams"),
headers=self._headers(token),
@@ -150,7 +173,7 @@ class Tools:
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
"""Create a new user (admin only).
"""Create a new user (requires admin token).
:param username: Unique username
:param email: User email address
@@ -188,7 +211,9 @@ class Tools:
payload["source_id"] = source_id
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.post(
self._api_url("/admin/users"),
headers=self._headers(token),
@@ -208,7 +233,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
return f"User '{username}' or email '{email}' already exists"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -219,7 +244,7 @@ class Tools:
__event_emitter__: Callable[[dict], Any] = None,
__event_call__: Callable[[dict], Any] = None,
) -> str:
"""Delete a user (admin only).
"""Delete a user (requires admin token).
:param username: Username to delete
"""
@@ -254,7 +279,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/admin/users/{username}"),
headers=self._headers(token),
@@ -270,7 +297,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -291,7 +318,7 @@ class Tools:
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
"""Edit a user (admin only).
"""Edit a user (requires admin token).
:param username: Username to edit
:param email: New email
@@ -346,7 +373,9 @@ class Tools:
payload["max_repo_creation"] = max_repo_creation
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.patch(
self._api_url(f"/admin/users/{username}"),
headers=self._headers(token),
@@ -366,7 +395,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -377,7 +406,7 @@ class Tools:
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
"""List all users (admin only).
"""List all users (requires admin token).
:param page: Page number
:param limit: Items per page
@@ -395,7 +424,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/admin/users?page={page}&limit={limit}"),
headers=self._headers(token),
@@ -433,7 +464,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -443,13 +474,13 @@ class Tools:
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
"""Get user details (admin only).
"""Get user details.
:param username: Username to get
"""
token = self._get_token(__user__)
if not token:
return "Error: GITEA_TOKEN not configured. Admin token required."
return "Error: GITEA_TOKEN not configured"
if __event_emitter__:
await __event_emitter__(
@@ -463,7 +494,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/users/{username}"),
headers=self._headers(token),
@@ -498,7 +531,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -547,21 +580,19 @@ class Tools:
payload = {
"username": org_name,
"full_name": full_name or org_name,
"description": description or "",
"website": website or "",
"location": location or "",
"visibility": visibility,
}
if full_name:
payload["full_name"] = full_name
if description:
payload["description"] = description
if website:
payload["website"] = website
if location:
payload["location"] = location
payload["visibility"] = visibility
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.post(
self._api_url("/admin/users"),
self._api_url("/orgs"), # FIXED: was /admin/users
headers=self._headers(token),
json=payload,
)
@@ -579,7 +610,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
return f"Organization '{org_name}' already exists"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -590,13 +621,13 @@ class Tools:
__event_emitter__: Callable[[dict], Any] = None,
__event_call__: Callable[[dict], Any] = None,
) -> str:
"""Delete an organization (admin only).
"""Delete an organization (requires organization owner permission).
:param org: Organization name
"""
token = self._get_token(__user__)
if not token:
return "Error: GITEA_TOKEN not configured. Admin token required."
return "Error: GITEA_TOKEN not configured"
if __event_call__:
result = await __event_call__(
@@ -625,7 +656,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/orgs/{org}"),
headers=self._headers(token),
@@ -641,7 +674,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Organization '{org}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -696,7 +729,9 @@ class Tools:
payload["visibility"] = visibility
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.patch(
self._api_url(f"/orgs/{org}"),
headers=self._headers(token),
@@ -716,7 +751,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Organization '{org}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -739,7 +774,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url("/user/orgs"),
headers=self._headers(token),
@@ -766,7 +803,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -797,7 +834,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/orgs/{effective_org}"),
headers=self._headers(token),
@@ -830,7 +869,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Organization not found: {effective_org}"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -864,7 +903,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/orgs/{effective_org}/members"),
headers=self._headers(token),
@@ -891,7 +932,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Organization '{effective_org}' not found or no access"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -927,7 +968,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.put(
self._api_url(f"/orgs/{effective_org}/members/{username}"),
headers=self._headers(token),
@@ -943,7 +986,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' or organization '{effective_org}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -979,7 +1022,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/orgs/{effective_org}/members/{username}"),
headers=self._headers(token),
@@ -995,7 +1040,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not in organization '{effective_org}'"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1029,7 +1074,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/orgs/{effective_org}/repos"),
headers=self._headers(token),
@@ -1060,7 +1107,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Organization '{effective_org}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1070,6 +1117,7 @@ class Tools:
org: Optional[str] = None,
description: str = "",
private: bool = False,
visibility: Optional[str] = None,
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
@@ -1078,7 +1126,8 @@ class Tools:
:param repo_name: Repository name
:param org: Organization name
:param description: Repository description
:param private: Make repository private
:param private: Make repository private (deprecated, use visibility)
:param visibility: Repository visibility (public/limited/private)
"""
token = self._get_token(__user__)
if not token:
@@ -1088,6 +1137,15 @@ class Tools:
if not effective_org:
return "Error: Organization not specified and no default set"
# Handle both old and new parameters
if visibility:
if visibility not in ["public", "limited", "private"]:
return f"Error: Invalid visibility '{visibility}'. Use: public, limited, private"
is_private = visibility == "private"
else:
is_private = private
visibility = "private" if private else "public"
if __event_emitter__:
await __event_emitter__(
{
@@ -1100,14 +1158,16 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.post(
self._api_url(f"/orgs/{effective_org}/repos"),
headers=self._headers(token),
json={
"name": repo_name,
"description": description,
"private": private,
"private": is_private,
"auto_init": True,
},
)
@@ -1121,17 +1181,16 @@ class Tools:
"data": {"description": "Done", "done": True, "hidden": True},
}
)
visibility = "Private" if private else "Public"
return f"Created repository: {effective_org}/{repo_name}\nVisibility: {visibility}\nDescription: {description or 'N/A'}\nURL: {repo.get('html_url', 'N/A')}"
return f"Created repository: {effective_org}/{repo_name}\nVisibility: {visibility.capitalize()}\nDescription: {description or 'N/A'}\nURL: {repo.get('html_url', 'N/A')}"
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
return f"Repository '{repo_name}' already exists in {effective_org}"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
# =========================================================================
# TEAMS (UPDATED - now uses team ID resolution)
# TEAMS
# =========================================================================
async def list_teams(
@@ -1161,7 +1220,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/orgs/{effective_org}/teams"),
headers=self._headers(token),
@@ -1179,7 +1240,7 @@ class Tools:
permission = team.get("permission", "unknown")
output += f"- {name} (ID: {team_id}): {desc}\n"
output += (
f" Permission: {permission} | {members} members | {repos} repos\n"
f" Permission: {permission} | {members} members | {repos} repos\n"
)
output += f"\nTotal: {len(teams)} teams"
@@ -1193,7 +1254,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1202,7 +1263,7 @@ class Tools:
name: str,
org: Optional[str] = None,
description: str = "",
permission: str = "push",
permission: str = "write",
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
@@ -1228,7 +1289,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.post(
self._api_url(f"/orgs/{effective_org}/teams"),
headers=self._headers(token),
@@ -1262,7 +1325,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
return f"Team '{name}' already exists in {effective_org}"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1314,7 +1377,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/teams/{team_id}"),
headers=self._headers(token),
@@ -1329,7 +1394,7 @@ class Tools:
response.raise_for_status()
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1366,7 +1431,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.put(
self._api_url(f"/teams/{team_id}/members/{username}"),
headers=self._headers(token),
@@ -1382,7 +1449,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1419,7 +1486,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/teams/{team_id}/members/{username}"),
headers=self._headers(token),
@@ -1435,7 +1504,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"User '{username}' not in team '{team_name}'"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1471,7 +1540,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/teams/{team_id}/members"),
headers=self._headers(token),
@@ -1498,7 +1569,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1537,7 +1608,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.put(
self._api_url(f"/teams/{team_id}/repos/{owner}/{repo_name}"),
headers=self._headers(token),
@@ -1551,7 +1624,7 @@ class Tools:
response.raise_for_status()
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1593,7 +1666,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/teams/{team_id}/repos/{owner}/{repo_name}"),
headers=self._headers(token),
@@ -1607,7 +1682,7 @@ class Tools:
response.raise_for_status()
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1643,7 +1718,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/teams/{team_id}/repos"),
headers=self._headers(token),
@@ -1666,7 +1743,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1678,7 +1755,7 @@ class Tools:
self,
username: str,
repo: str,
permission: str = "push",
permission: str = "write",
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
@@ -1704,7 +1781,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.put(
self._api_url(
f"/repos/{owner}/{repo_name}/collaborators/{username}"
@@ -1727,7 +1806,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Repository {owner}/{repo_name} or user '{username}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1760,7 +1839,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(
f"/repos/{owner}/{repo_name}/collaborators/{username}"
@@ -1783,7 +1864,7 @@ class Tools:
response.raise_for_status()
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1815,7 +1896,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/repos/{owner}/{repo_name}/collaborators"),
headers=self._headers(token),
@@ -1830,7 +1913,7 @@ class Tools:
role = (
"admin"
if perm.get("admin")
else "push" if perm.get("push") else "pull"
else "write" if perm.get("push") else "read"
)
output += f"- {login} ({role})\n"
@@ -1845,7 +1928,7 @@ class Tools:
)
return output
except httpx.HTTPStatusError as e:
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1858,6 +1941,7 @@ class Tools:
repo_name: str,
description: str = "",
private: bool = False,
visibility: Optional[str] = None,
__user__: dict = None,
__event_emitter__: Callable[[dict], Any] = None,
) -> str:
@@ -1865,12 +1949,22 @@ class Tools:
:param repo_name: Repository name
:param description: Repository description
:param private: Make repository private
:param private: Make repository private (deprecated, use visibility)
:param visibility: Repository visibility (public/limited/private)
"""
token = self._get_token(__user__)
if not token:
return "Error: GITEA_TOKEN not configured"
# Handle both old and new parameters
if visibility:
if visibility not in ["public", "limited", "private"]:
return f"Error: Invalid visibility '{visibility}'. Use: public, limited, private"
is_private = visibility == "private"
else:
is_private = private
visibility = "private" if private else "public"
if __event_emitter__:
await __event_emitter__(
{
@@ -1883,14 +1977,16 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.post(
self._api_url("/user/repos"),
headers=self._headers(token),
json={
"name": repo_name,
"description": description,
"private": private,
"private": is_private,
"auto_init": True,
},
)
@@ -1904,12 +2000,11 @@ class Tools:
"data": {"description": "Done", "done": True, "hidden": True},
}
)
visibility = "Private" if private else "Public"
return f"Created repository: {repo_name}\nVisibility: {visibility}\nDescription: {description or 'N/A'}\nURL: {repo.get('html_url', 'N/A')}"
return f"Created repository: {repo_name}\nVisibility: {visibility.capitalize()}\nDescription: {description or 'N/A'}\nURL: {repo.get('html_url', 'N/A')}"
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
return f"Repository '{repo_name}' already exists"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -1960,7 +2055,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.delete(
self._api_url(f"/repos/{owner}/{repo_name}"),
headers=self._headers(token),
@@ -1976,7 +2073,7 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Repository '{owner}/{repo_name}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
@@ -2011,7 +2108,9 @@ class Tools:
)
try:
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
async with httpx.AsyncClient(
timeout=30.0, verify=self.valves.VERIFY_SSL
) as client:
response = await client.get(
self._api_url(f"/repos/{owner}/{repo_name}"),
headers=self._headers(token),
@@ -2047,6 +2146,6 @@ class Tools:
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Repository '{owner}/{repo_name}' not found"
return f"HTTP Error {e.response.status_code}: {e.response.text[:200]}"
return self._format_error(e)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"