"""Gitea API tools using per-user access tokens.
Provides repository, issue, PR, commit, file, notification, and star
operations via the Gitea REST API. Supports custom base URLs for
self-hosted instances. Requires set_user_api_key service=gitea.
"""
from __future__ import annotations
import base64
import json
import logging
from typing import Any, TYPE_CHECKING
import aiohttp
if TYPE_CHECKING:
from tool_context import ToolContext
logger = logging.getLogger(__name__)
async def _gitea_request(
method: str,
path: str,
token: str,
base_url: str,
*,
params: dict[str, Any] | None = None,
json_body: dict[str, Any] | None = None,
) -> dict[str, Any] | list[Any] | str:
"""Make a Gitea API request. base_url should not include /api/v1."""
base = base_url.rstrip("/")
url = f"{base}/api/v1{path}"
headers = {
"Authorization": f"token {token}",
"Accept": "application/json",
}
async with aiohttp.ClientSession() as session:
async with session.request(
method, url, headers=headers, params=params, json=json_body
) as resp:
if resp.status == 204:
return {"status": "success", "code": 204}
body = await resp.text()
if resp.status >= 400:
return {"error": f"Gitea API error ({resp.status})", "detail": body[:2000]}
if not body:
return {}
try:
return json.loads(body)
except json.JSONDecodeError:
return body[:4000]
async def _get_credentials(ctx: ToolContext | None) -> tuple[str, str]:
"""Return (token, base_url). Raises if not configured."""
if ctx is None or ctx.redis is None:
raise RuntimeError("Context or Redis not available")
from tools.manage_api_keys import get_gitea_credentials, missing_api_key_error
creds = await get_gitea_credentials(
ctx.user_id,
redis_client=ctx.redis,
channel_id=getattr(ctx, "channel_id", None),
fallback_to_pool=True,
config=getattr(ctx, "config", None),
)
if not creds:
raise RuntimeError(missing_api_key_error("gitea"))
return creds
def _fmt_repo(r: dict) -> dict:
return {
"full_name": r.get("full_name"),
"description": r.get("description"),
"private": r.get("private"),
"fork": r.get("fork"),
"stars_count": r.get("stars_count"),
"forks_count": r.get("forks_count"),
"default_branch": r.get("default_branch"),
"html_url": r.get("html_url"),
"updated_at": r.get("updated_at"),
}
def _fmt_issue(i: dict) -> dict:
user = i.get("user") or {}
return {
"number": i.get("number"),
"title": i.get("title"),
"state": i.get("state"),
"user": user.get("login") if isinstance(user, dict) else None,
"labels": [l.get("name") for l in i.get("labels", []) if isinstance(l, dict)],
"created_at": i.get("created_at"),
"updated_at": i.get("updated_at"),
"html_url": i.get("html_url"),
"body": (i.get("body") or "")[:1000],
}
def _fmt_pr(p: dict) -> dict:
user = p.get("user") or {}
return {
"number": p.get("number"),
"title": p.get("title"),
"state": p.get("state"),
"user": user.get("login") if isinstance(user, dict) else None,
"draft": p.get("draft"),
"merged": p.get("merged"),
"created_at": p.get("created_at"),
"updated_at": p.get("updated_at"),
"html_url": p.get("html_url"),
"additions": p.get("additions"),
"deletions": p.get("deletions"),
"changed_files": p.get("changed_files"),
}
# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------
[docs]
async def gitea_list_repos(
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List the authenticated user's Gitea repositories."""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request("GET", "/user/repos", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
repos = [_fmt_repo(r) for r in data] if isinstance(data, list) else []
return json.dumps({"count": len(repos), "repos": repos})
[docs]
async def gitea_search_repos(
q: str,
sort: str = "updated",
order: str = "desc",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""Search Gitea repositories by keyword."""
token, base_url = await _get_credentials(ctx)
params = {"q": q, "sort": sort, "order": order, "page": page, "limit": min(limit, 50)}
data = await _gitea_request("GET", "/repos/search", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
items = data.get("data", []) if isinstance(data, dict) else []
repos = [_fmt_repo(r) for r in items]
return json.dumps({"count": len(repos), "repos": repos})
[docs]
async def gitea_get_repo(
owner: str,
repo: str,
ctx: ToolContext | None = None,
) -> str:
"""Get details of a Gitea repository."""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request("GET", f"/repos/{owner}/{repo}", token, base_url)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
[docs]
async def gitea_create_repo(
name: str,
description: str = "",
private: bool = False,
auto_init: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Create a new Gitea repository for the authenticated user."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"name": name}
if description:
payload["description"] = description
payload["private"] = private
payload["auto_init"] = auto_init
data = await _gitea_request("POST", "/user/repos", token, base_url, json_body=payload)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
[docs]
async def gitea_list_issues(
owner: str,
repo: str,
state: str = "open",
labels: str = "",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List issues on a Gitea repository."""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {"state": state, "page": page, "limit": min(limit, 50)}
if labels:
params["labels"] = labels
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/issues", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
issues = [_fmt_issue(i) for i in data] if isinstance(data, list) else []
return json.dumps({"count": len(issues), "issues": issues})
[docs]
async def gitea_get_issue(
owner: str,
repo: str,
index: int,
ctx: ToolContext | None = None,
) -> str:
"""Get a single issue by index."""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/issues/{index}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_create_issue(
owner: str,
repo: str,
title: str,
body: str = "",
labels: list[int] | None = None,
assignees: list[str] | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Create a new issue on a Gitea repository."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"title": title}
if body:
payload["body"] = body
if labels:
payload["labels"] = labels
if assignees:
payload["assignees"] = assignees
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/issues", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_update_issue(
owner: str,
repo: str,
index: int,
title: str | None = None,
body: str | None = None,
state: str | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Update an existing issue."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {}
if title is not None:
payload["title"] = title
if body is not None:
payload["body"] = body
if state is not None:
payload["state"] = state
data = await _gitea_request(
"PATCH", f"/repos/{owner}/{repo}/issues/{index}", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_list_pull_requests(
owner: str,
repo: str,
state: str = "open",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List pull requests on a Gitea repository."""
token, base_url = await _get_credentials(ctx)
params = {"state": state, "page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/pulls", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
prs = [_fmt_pr(p) for p in data] if isinstance(data, list) else []
return json.dumps({"count": len(prs), "pull_requests": prs})
[docs]
async def gitea_get_pull_request(
owner: str,
repo: str,
index: int,
ctx: ToolContext | None = None,
) -> str:
"""Get details of a specific pull request."""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/pulls/{index}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
result = _fmt_pr(data) if isinstance(data, dict) else {"raw": data}
if isinstance(data, dict) and data.get("body"):
result["body"] = (data.get("body") or "")[:2000]
return json.dumps(result)
[docs]
async def gitea_create_pull_request(
owner: str,
repo: str,
title: str,
head: str,
base: str = "main",
body: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Create a new pull request."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"title": title, "head": head, "base": base}
if body:
payload["body"] = body
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/pulls", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_pr(data) if isinstance(data, dict) else data)
[docs]
async def gitea_merge_pull_request(
owner: str,
repo: str,
index: int,
merge_style: str = "merge",
delete_branch_after_merge: bool = False,
ctx: ToolContext | None = None,
) -> str:
"""Merge a pull request. merge_style: merge, rebase, rebase-merge, squash."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"Do": merge_style}
if delete_branch_after_merge:
payload["delete_branch_after_merge"] = True
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/pulls/{index}/merge",
token, base_url, json_body=payload,
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps({"status": "success", "message": "Pull request merged"})
[docs]
async def gitea_get_file(
owner: str,
repo: str,
path: str,
ref: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Get the contents of a file or directory listing from a Gitea repository."""
token, base_url = await _get_credentials(ctx)
params = {}
if ref:
params["ref"] = ref
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/contents/{path}", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
if isinstance(data, dict) and data.get("type") == "file":
content_b64 = data.get("content", "")
try:
content = base64.b64decode(content_b64).decode("utf-8", errors="replace")
except Exception:
content = "(binary file)"
return json.dumps({
"name": data.get("name"),
"path": data.get("path"),
"size": data.get("size"),
"sha": data.get("sha"),
"content": content[:16000],
"html_url": data.get("html_url"),
})
if isinstance(data, list):
entries = [
{"name": e.get("name"), "type": e.get("type"), "path": e.get("path")}
for e in data
]
return json.dumps({"type": "directory", "entries": entries})
return json.dumps(data if isinstance(data, dict) else {"raw": str(data)[:4000]})
[docs]
async def gitea_list_commits(
owner: str,
repo: str,
sha: str = "",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List commits in a Gitea repository."""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {"page": page, "limit": min(limit, 50)}
if sha:
params["sha"] = sha
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/commits", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
commits = []
for c in (data if isinstance(data, list) else []):
commit = c.get("commit") or {}
author = commit.get("author") or {}
commits.append({
"sha": c.get("sha"),
"message": commit.get("message", "")[:500],
"author": author.get("name"),
"date": author.get("date"),
"html_url": c.get("html_url"),
})
return json.dumps({"count": len(commits), "commits": commits})
[docs]
async def gitea_get_commit(
owner: str,
repo: str,
sha: str,
ctx: ToolContext | None = None,
) -> str:
"""Get a single commit by SHA."""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/git/commits/{sha}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
commit = data.get("commit") or {} if isinstance(data, dict) else {}
author = commit.get("author") or {}
return json.dumps({
"sha": data.get("sha") if isinstance(data, dict) else None,
"message": commit.get("message", "")[:2000],
"author": author.get("name"),
"date": author.get("date"),
"html_url": data.get("html_url") if isinstance(data, dict) else None,
"stats": data.get("stats") if isinstance(data, dict) else None,
})
[docs]
async def gitea_list_notifications(
all_notifications: bool = False,
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List the user's Gitea notifications."""
token, base_url = await _get_credentials(ctx)
params = {"all": str(all_notifications).lower(), "page": page, "limit": min(limit, 50)}
data = await _gitea_request("GET", "/notifications", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
notifs = []
for n in (data if isinstance(data, list) else []):
subject = n.get("subject") or {}
notifs.append({
"id": n.get("id"),
"unread": n.get("unread"),
"subject_title": subject.get("title"),
"subject_type": subject.get("type"),
"repository": n.get("repository", {}).get("full_name") if isinstance(n.get("repository"), dict) else None,
"updated_at": n.get("updated_at"),
})
return json.dumps({"count": len(notifs), "notifications": notifs})
[docs]
async def gitea_star_repo(
owner: str,
repo: str,
star: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Star or unstar a Gitea repository."""
token, base_url = await _get_credentials(ctx)
method = "PUT" if star else "DELETE"
data = await _gitea_request(
method, f"/user/starred/{owner}/{repo}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
action = "starred" if star else "unstarred"
return json.dumps({"status": "success", "action": action, "repo": f"{owner}/{repo}"})
[docs]
async def gitea_list_branches(
owner: str,
repo: str,
page: int = 1,
limit: int = 30,
ctx: ToolContext | None = None,
) -> str:
"""List branches in a Gitea repository."""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/branches", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
branches = [
{"name": b.get("name"), "protected": b.get("protected"), "commit_sha": (b.get("commit") or {}).get("sha")}
for b in (data if isinstance(data, list) else [])
]
return json.dumps({"count": len(branches), "branches": branches})
[docs]
async def gitea_create_repo_from_template(
template_owner: str,
template_repo: str,
name: str,
description: str = "",
private: bool = False,
git_content: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Create a new repository from a template repository."""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {
"name": name,
"private": private,
"git_content": git_content,
}
if description:
payload["description"] = description
data = await _gitea_request(
"POST", f"/repos/{template_owner}/{template_repo}/generate",
token, base_url, json_body=payload,
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
# ---------------------------------------------------------------------------
# Tool registration (multi-tool format)
# ---------------------------------------------------------------------------
TOOLS = [
{
"name": "gitea_list_repos",
"description": "List the authenticated user's Gitea repositories. Supports custom base URLs for self-hosted instances.",
"parameters": {
"type": "object",
"properties": {
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page (max 50)"},
},
},
"handler": gitea_list_repos,
},
{
"name": "gitea_search_repos",
"description": "Search Gitea repositories by keyword.",
"parameters": {
"type": "object",
"properties": {
"q": {"type": "string", "description": "Search keyword"},
"sort": {"type": "string", "enum": ["alpha", "created", "updated", "size", "stars", "forks", "id"], "description": "Sort order"},
"order": {"type": "string", "enum": ["asc", "desc"], "description": "Sort direction"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["q"],
},
"handler": gitea_search_repos,
},
{
"name": "gitea_get_repo",
"description": "Get details of a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
},
"required": ["owner", "repo"],
},
"handler": gitea_get_repo,
},
{
"name": "gitea_create_repo",
"description": "Create a new Gitea repository for the authenticated user.",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Repository name"},
"description": {"type": "string", "description": "Repository description"},
"private": {"type": "boolean", "description": "Whether the repo is private"},
"auto_init": {"type": "boolean", "description": "Initialize with README"},
},
"required": ["name"],
},
"handler": gitea_create_repo,
},
{
"name": "gitea_list_issues",
"description": "List issues on a Gitea repository with optional filters.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"state": {"type": "string", "enum": ["open", "closed", "all"], "description": "Filter by state"},
"labels": {"type": "string", "description": "Comma-separated label names"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_issues,
},
{
"name": "gitea_get_issue",
"description": "Get a single issue by index.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Issue number"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_get_issue,
},
{
"name": "gitea_create_issue",
"description": "Create a new issue on a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"title": {"type": "string", "description": "Issue title"},
"body": {"type": "string", "description": "Issue body (Markdown)"},
"labels": {"type": "array", "items": {"type": "integer"}, "description": "Label IDs"},
"assignees": {"type": "array", "items": {"type": "string"}, "description": "Assignee usernames"},
},
"required": ["owner", "repo", "title"],
},
"handler": gitea_create_issue,
},
{
"name": "gitea_update_issue",
"description": "Update an existing issue (title, body, state).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Issue number"},
"title": {"type": "string", "description": "New title"},
"body": {"type": "string", "description": "New body"},
"state": {"type": "string", "enum": ["open", "closed"], "description": "New state"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_update_issue,
},
{
"name": "gitea_list_pull_requests",
"description": "List pull requests on a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"state": {"type": "string", "enum": ["open", "closed", "all"], "description": "Filter by state"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_pull_requests,
},
{
"name": "gitea_get_pull_request",
"description": "Get details of a specific pull request.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Pull request number"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_get_pull_request,
},
{
"name": "gitea_create_pull_request",
"description": "Create a new pull request.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"title": {"type": "string", "description": "PR title"},
"head": {"type": "string", "description": "Head branch (or owner:branch for forks)"},
"base": {"type": "string", "description": "Base branch (default: main)"},
"body": {"type": "string", "description": "PR body (Markdown)"},
},
"required": ["owner", "repo", "title", "head"],
},
"handler": gitea_create_pull_request,
},
{
"name": "gitea_merge_pull_request",
"description": "Merge a pull request. merge_style: merge, rebase, rebase-merge, squash.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Pull request number"},
"merge_style": {"type": "string", "enum": ["merge", "rebase", "rebase-merge", "squash"], "description": "Merge method"},
"delete_branch_after_merge": {"type": "boolean", "description": "Delete head branch after merge"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_merge_pull_request,
},
{
"name": "gitea_get_file",
"description": "Get the contents of a file or directory listing from a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"path": {"type": "string", "description": "File or directory path"},
"ref": {"type": "string", "description": "Branch, tag, or commit SHA"},
},
"required": ["owner", "repo", "path"],
},
"handler": gitea_get_file,
},
{
"name": "gitea_list_commits",
"description": "List commits in a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"sha": {"type": "string", "description": "Branch or SHA to list from"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_commits,
},
{
"name": "gitea_get_commit",
"description": "Get a single commit by SHA.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"sha": {"type": "string", "description": "Commit SHA or ref"},
},
"required": ["owner", "repo", "sha"],
},
"handler": gitea_get_commit,
},
{
"name": "gitea_list_notifications",
"description": "List the user's Gitea notifications (issues, PRs, etc.).",
"parameters": {
"type": "object",
"properties": {
"all_notifications": {"type": "boolean", "description": "Include read notifications"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
},
"handler": gitea_list_notifications,
},
{
"name": "gitea_star_repo",
"description": "Star or unstar a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"star": {"type": "boolean", "description": "True to star, false to unstar"},
},
"required": ["owner", "repo"],
},
"handler": gitea_star_repo,
},
{
"name": "gitea_list_branches",
"description": "List branches in a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_branches,
},
{
"name": "gitea_create_repo_from_template",
"description": "Create a new repository from a template repository.",
"parameters": {
"type": "object",
"properties": {
"template_owner": {"type": "string", "description": "Template repo owner"},
"template_repo": {"type": "string", "description": "Template repo name"},
"name": {"type": "string", "description": "Name for the new repo"},
"description": {"type": "string", "description": "Description"},
"private": {"type": "boolean", "description": "Whether private"},
"git_content": {"type": "boolean", "description": "Include git content from template"},
},
"required": ["template_owner", "template_repo", "name"],
},
"handler": gitea_create_repo_from_template,
},
]