Update gitea/admin.py
This commit is contained in:
285
gitea/admin.py
285
gitea/admin.py
@@ -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}"
|
||||
Reference in New Issue
Block a user