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