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