diff --git a/gitea/admin.py b/gitea/admin.py index 6a6e052..2780833 100644 --- a/gitea/admin.py +++ b/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}" \ No newline at end of file