diff --git a/gitea-manager/SKILL.md b/gitea-manager/SKILL.md new file mode 100644 index 0000000..ee62f36 --- /dev/null +++ b/gitea-manager/SKILL.md @@ -0,0 +1,339 @@ +--- +name: gitea-manager +description: 管理 Gitea 仓库的全能工具,支持仓库创建删除、文件操作、分支管理、Issue/PR 管理、组织和用户操作、仓库设置配置等。使用场景:(1) 创建或删除 Gitea 仓库 (2) 管理仓库文件和目录 (3) 操作分支和保护规则 (4) 创建和管理 Issue 和 Pull Request (5) 管理组织和团队成员 (6) 配置 Webhook 和 Deploy Key (7) 搜索仓库和 Issue。必需环境变量:GITEA_URL(实例地址)和 GITEA_TOKEN(API 令牌)。 +--- + +# Gitea Manager + +## 快速开始 + +### 环境配置 + +使用此 skill 前需要配置 Gitea 连接信息: + +```bash +export GITEA_URL="https://gitea.example.com" +export GITEA_TOKEN="your-api-token-here" +``` + +可以通过以下方式获取 API Token: +1. 登录 Gitea +2. 进入「设置」→「应用」 +3. 创建新的 API Token + +### 初始化客户端 + +在 Python 代码中使用: + +```python +import sys +sys.path.append("/root/.claude/skills/gitea-manager/scripts") +from gitea_client import GiteaClient + +client = GiteaClient() +``` + +或指定自定义配置: + +```python +client = GiteaClient(url="https://gitea.example.com", token="your-token") +``` + +## 仓库操作 + +### 列出仓库 + +```python +repos = client.list_my_repos() +for r in repos: + print(f"{r['full_name']}: {r.get('description', '')}") +``` + +### 创建仓库 + +```python +repo = client.create_repo( + name="my-new-repo", + description="这是一个新仓库", + private=False, + auto_init=True +) +``` + +### 在组织下创建仓库 + +```python +repo = client.create_org_repo( + org="my-org", + name="org-repo", + description="组织仓库", + private=True +) +``` + +### 获取仓库信息 + +```python +repo = client.get_repo("owner", "repo-name") +``` + +### 删除仓库 + +```python +client.delete_repo("owner", "repo-name") +``` + +## 文件操作 + +### 读取文件内容 + +```python +content = client.get_file_content("owner", "repo", "path/to/file.txt", ref="main") +``` + +### 获取文件信息 + +```python +info = client.get_file_info("owner", "repo", "path/to/file.txt", ref="main") +``` + +### 创建或更新文件 + +```python +client.create_or_update_file( + owner="owner", + repo="repo", + path="new-file.txt", + content="文件内容", + message="添加新文件", + branch="main", + email="user@example.com", + name="Username" +) +``` + +### 删除文件 + +```python +info = client.get_file_info("owner", "repo", "file.txt") +client.delete_file( + owner="owner", + repo="repo", + path="file.txt", + message="删除文件", + branch="main", + sha=info["sha"], + email="user@example.com", + name="Username" +) +``` + +### 列出目录 + +```python +files = client.list_directory("owner", "repo", "path/to/dir", ref="main") +``` + +## 分支操作 + +### 列出分支 + +```python +branches = client.list_branches("owner", "repo") +``` + +### 获取分支信息 + +```python +branch = client.get_branch("owner", "repo", "main") +``` + +### 创建分支 + +```python +client.create_branch("owner", "repo", "new-branch", "main") +``` + +### 删除分支 + +```python +client.delete_branch("owner", "repo", "old-branch") +``` + +### 设置分支保护 + +```python +client.set_protected_branch( + owner="owner", + repo="repo", + branch="main", + enable_push=True, + enable_push_whitelist=["developers"], + require_pull_request=True, + require_approvals=2 +) +``` + +## Issue 操作 + +### 列出 Issue + +```python +issues = client.list_issues("owner", "repo", state="open") +``` + +### 创建 Issue + +```python +issue = client.create_issue( + owner="owner", + repo="repo", + title="Bug: 登录失败", + body="详细描述问题..." +) +``` + +### 更新 Issue + +```python +client.update_issue("owner", "repo", issue_index, title="新标题", state="closed") +``` + +### 添加评论 + +```python +comment = client.create_issue_comment("owner", "repo", issue_index, "这是我的评论") +``` + +## Pull Request 操作 + +### 列出 PR + +```python +pulls = client.list_pulls("owner", "repo", state="open") +``` + +### 创建 PR + +```python +pr = client.create_pull( + owner="owner", + repo="repo", + title="Add new feature", + body="PR 描述...", + head="feature-branch", + base="main" +) +``` + +### 合并 PR + +```python +client.merge_pull("owner", "repo", pr_index, merge_style="merge") +``` + +## 组织操作 + +### 列出组织 + +```python +orgs = client.list_orgs() +``` + +### 列出组织仓库 + +```python +repos = client.list_org_repos("my-org") +``` + +### 创建组织 + +```python +org = client.create_org( + username="new-org", + email="org@example.com" +) +``` + +### 列出组织团队 + +```python +teams = client.list_org_teams("my-org") +``` + +## Webhook 操作 + +### 列出 Webhook + +```python +hooks = client.list_hooks("owner", "repo") +``` + +### 创建 Webhook + +```python +hook = client.create_hook( + owner="owner", + repo="repo", + config={"url": "https://example.com/webhook", "secret": "secret"}, + events=["push", "issue", "pull_request"], + active=True +) +``` + +### 删除 Webhook + +```python +client.delete_hook("owner", "repo", hook_id) +``` + +## Deploy Key 操作 + +### 列出 Deploy Key + +```python +keys = client.list_deploy_keys("owner", "repo") +``` + +### 创建 Deploy Key + +```python +key = client.create_deploy_key( + owner="owner", + repo="repo", + title="my-deploy-key", + key="ssh-rsa AAAA...", + read_only=True +) +``` + +### 删除 Deploy Key + +```python +client.delete_deploy_key("owner", "repo", key_id) +``` + +## 搜索操作 + +### 搜索仓库 + +```python +repos = client.search_repos("keyword", limit=20) +``` + +### 搜索 Issue + +```python +issues = client.search_issues("owner", "repo", "bug", limit=20) +``` + +## 命令行使用 + +脚本也支持命令行调用: + +```bash +python gitea_client.py list-repos +python gitea_client.py get-repo owner/repo +python gitea_client.py create-repo my-repo "描述" +python gitea_client.py delete-repo owner/repo +``` diff --git a/gitea-manager/gitea-manager.skill b/gitea-manager/gitea-manager.skill new file mode 100644 index 0000000..16c7629 Binary files /dev/null and b/gitea-manager/gitea-manager.skill differ diff --git a/gitea-manager/gitea_client.py b/gitea-manager/gitea_client.py new file mode 100644 index 0000000..90258cb --- /dev/null +++ b/gitea-manager/gitea_client.py @@ -0,0 +1,423 @@ +#!/usr/bin/env python3 +"""Gitea API Client - 封装 Gitea REST API 的所有操作""" + +import os +import json +import requests +from typing import Optional, Dict, List, Any + + +class GiteaClient: + """Gitea API 客户端""" + + def __init__(self, url: Optional[str] = None, token: Optional[str] = None): + self.url = url or os.environ.get("GITEA_URL", "http://localhost:3000") + self.token = token or os.environ.get("GITEA_TOKEN", "") + self.session = requests.Session() + self.session.headers.update({ + "Authorization": f"token {self.token}", + "Content-Type": "application/json", + "Accept": "application/json" + }) + if not self.token: + raise ValueError("GITEA_TOKEN 环境变量未设置") + + def _request(self, method: str, path: str, **kwargs) -> requests.Response: + """发送 API 请求""" + url = f"{self.url.rstrip('/')}{path}" + response = self.session.request(method, url, **kwargs) + response.raise_for_status() + return response + + # ==================== 用户操作 ==================== + + def get_current_user(self) -> Dict: + """获取当前认证用户信息""" + return self._request("GET", "/user").json() + + def get_user(self, username: str) -> Dict: + """获取指定用户信息""" + return self._request("GET", f"/users/{username}").json() + + def list_user_repos(self, username: str, page: int = 1, limit: int = 50) -> List[Dict]: + """列出用户的所有仓库""" + return self._request("GET", f"/users/{username}/repos", params={"page": page, "limit": limit}).json() + + def create_user(self, email: str, username: str, password: str, **kwargs) -> Dict: + """创建用户(需要管理员权限)""" + data = {"email": email, "username": username, "password": password} + data.update(kwargs) + return self._request("POST", "/admin/users", json=data).json() + + def delete_user(self, username: str) -> None: + """删除用户(需要管理员权限)""" + self._request("DELETE", f"/admin/users/{username}") + + # ==================== 组织操作 ==================== + + def list_orgs(self, page: int = 1, limit: int = 50) -> List[Dict]: + """列出当前用户所属组织""" + return self._request("GET", "/user/orgs", params={"page": page, "limit": limit}).json() + + def get_org(self, username: str) -> Dict: + """获取组织信息""" + return self._request("GET", f"/orgs/{username}").json() + + def create_org(self, username: str, email: str, **kwargs) -> Dict: + """创建组织""" + data = {"username": username, "email": email} + data.update(kwargs) + return self._request("POST", "/admin/orgs", json=data).json() + + def list_org_repos(self, org: str, page: int = 1, limit: int = 50) -> List[Dict]: + """列出组织的所有仓库""" + return self._request("GET", f"/orgs/{org}/repos", params={"page": page, "limit": limit}).json() + + # ==================== 仓库操作 ==================== + + def list_my_repos(self, page: int = 1, limit: int = 50) -> List[Dict]: + """列出当前用户的仓库""" + return self._request("GET", "/user/repos", params={"page": page, "limit": limit}).json() + + def get_repo(self, owner: str, repo: str) -> Dict: + """获取仓库信息""" + return self._request("GET", f"/repos/{owner}/{repo}").json() + + def create_repo(self, name: str, description: str = "", private: bool = False, + auto_init: bool = True, **kwargs) -> Dict: + """创建仓库""" + data = { + "name": name, + "description": description, + "private": private, + "auto_init": auto_init + } + data.update(kwargs) + return self._request("POST", "/user/repos", json=data).json() + + def create_org_repo(self, org: str, name: str, description: str = "", + private: bool = False, auto_init: bool = True, **kwargs) -> Dict: + """在组织下创建仓库""" + data = { + "name": name, + "description": description, + "private": private, + "auto_init": auto_init + } + data.update(kwargs) + return self._request("POST", f"/orgs/{org}/repos", json=data).json() + + def delete_repo(self, owner: str, repo: str) -> None: + """删除仓库""" + self._request("DELETE", f"/repos/{owner}/{repo}") + + def get_repo_info(self, owner: str, repo: str) -> Dict: + """获取仓库详细信息""" + return self._request("GET", f"/repos/{owner}/{repo}").json() + + def update_repo(self, owner: str, repo: str, **kwargs) -> Dict: + """更新仓库设置""" + return self._request("PATCH", f"/repos/{owner}/{repo}", json=kwargs).json() + + # ==================== 分支操作 ==================== + + def list_branches(self, owner: str, repo: str, page: int = 1, limit: int = 50) -> List[Dict]: + """列出仓库分支""" + return self._request("GET", f"/repos/{owner}/{repo}/branches", + params={"page": page, "limit": limit}).json() + + def get_branch(self, owner: str, repo: str, branch: str) -> Dict: + """获取分支信息""" + return self._request("GET", f"/repos/{owner}/{repo}/branches/{branch}").json() + + def create_branch(self, owner: str, repo: str, branch_name: str, from_branch: str) -> Dict: + """创建分支""" + data = { + "new_branch_name": branch_name, + "from_branch": from_branch + } + return self._request("POST", f"/repos/{owner}/{repo}/branches", json=data).json() + + def delete_branch(self, owner: str, repo: str, branch: str) -> None: + """删除分支""" + self._request("DELETE", f"/repos/{owner}/{repo}/branches/{branch}") + + def list_protected_branches(self, owner: str, repo: str) -> List[Dict]: + """列出受保护分支""" + return self._request("GET", f"/repos/{owner}/{repo}/protected_branches").json() + + def get_protected_branch(self, owner: str, repo: str, branch: str) -> Dict: + """获取受保护分支规则""" + return self._request("GET", f"/repos/{owner}/{repo}/protected_branches/{branch}").json() + + def set_protected_branch(self, owner: str, repo: str, branch: str, **kwargs) -> Dict: + """设置分支保护规则""" + return self._request("PUT", f"/repos/{owner}/{repo}/protected_branches/{branch}", json=kwargs).json() + + # ==================== 文件操作 ==================== + + def get_file_content(self, owner: str, repo: str, path: str, ref: str = "main") -> str: + """获取文件内容(Base64 编码)""" + response = self._request("GET", f"/repos/{owner}/{repo}/raw/{path}", params={"ref": ref}) + import base64 + return base64.b64encode(response.content).decode('utf-8') + + def get_file_info(self, owner: str, repo: str, path: str, ref: str = "main") -> Dict: + """获取文件信息""" + return self._request("GET", f"/repos/{owner}/{repo}/contents/{path}", params={"ref": ref}).json() + + def create_or_update_file(self, owner: str, repo: str, path: str, content: str, + message: str, branch: str, email: str, name: str) -> Dict: + """创建或更新文件""" + import base64 + data = { + "content": base64.b64encode(content.encode()).decode('utf-8'), + "message": message, + "branch": branch, + "author": {"email": email, "name": name}, + "committer": {"email": email, "name": name} + } + return self._request("POST", f"/repos/{owner}/{repo}/contents/{path}", json=data).json() + + def delete_file(self, owner: str, repo: str, path: str, message: str, + branch: str, sha: str, email: str, name: str) -> Dict: + """删除文件""" + data = { + "message": message, + "branch": branch, + "sha": sha, + "author": {"email": email, "name": name}, + "committer": {"email": email, "name": name} + } + return self._request("DELETE", f"/repos/{owner}/{repo}/contents/{path}", json=data).json() + + def list_directory(self, owner: str, repo: str, path: str = "", + ref: str = "main", page: int = 1, limit: int = 100) -> List[Dict]: + """列出目录内容""" + return self._request("GET", f"/repos/{owner}/{repo}/contents/{path}", + params={"ref": ref, "page": page, "limit": limit}).json() + + # ==================== Issue 操作 ==================== + + def list_issues(self, owner: str, repo: str, state: str = "open", + page: int = 1, limit: int = 50, **kwargs) -> List[Dict]: + """列出仓库的 Issue""" + params = {"state": state, "page": page, "limit": limit} + params.update(kwargs) + return self._request("GET", f"/repos/{owner}/{repo}/issues", params=params).json() + + def get_issue(self, owner: str, repo: str, index: int) -> Dict: + """获取 Issue 详情""" + return self._request("GET", f"/repos/{owner}/{repo}/issues/{index}").json() + + def create_issue(self, owner: str, repo: str, title: str, body: str = "", + **kwargs) -> Dict: + """创建 Issue""" + data = {"title": title, "body": body} + data.update(kwargs) + return self._request("POST", f"/repos/{owner}/{repo}/issues", json=data).json() + + def update_issue(self, owner: str, repo: str, index: int, **kwargs) -> Dict: + """更新 Issue""" + return self._request("PATCH", f"/repos/{owner}/{repo}/issues/{index}", json=kwargs).json() + + def close_issue(self, owner: str, repo: str, index: int) -> Dict: + """关闭 Issue""" + return self.update_issue(owner, repo, index, state="closed") + + def list_issue_comments(self, owner: str, repo: str, issue_index: int, + page: int = 1, limit: int = 50) -> List[Dict]: + """列出 Issue 的评论""" + return self._request("GET", f"/repos/{owner}/{repo}/issues/{issue_index}/comments", + params={"page": page, "limit": limit}).json() + + def create_issue_comment(self, owner: str, repo: str, issue_index: int, body: str) -> Dict: + """创建 Issue 评论""" + return self._request("POST", f"/repos/{owner}/{repo}/issues/{issue_index}/comments", + json={"body": body}).json() + + # ==================== Pull Request 操作 ==================== + + def list_pulls(self, owner: str, repo: str, state: str = "open", + page: int = 1, limit: int = 50) -> List[Dict]: + """列出 Pull Request""" + return self._request("GET", f"/repos/{owner}/{repo}/pulls", + params={"state": state, "page": page, "limit": limit}).json() + + def get_pull(self, owner: str, repo: str, index: int) -> Dict: + """获取 Pull Request 详情""" + return self._request("GET", f"/repos/{owner}/{repo}/pulls/{index}").json() + + def create_pull(self, owner: str, repo: str, title: str, body: str, + head: str, base: str, **kwargs) -> Dict: + """创建 Pull Request""" + data = { + "title": title, + "body": body, + "head": head, + "base": base + } + data.update(kwargs) + return self._request("POST", f"/repos/{owner}/{repo}/pulls", json=data).json() + + def update_pull(self, owner: str, repo: str, index: int, **kwargs) -> Dict: + """更新 Pull Request""" + return self._request("PATCH", f"/repos/{owner}/{repo}/pulls/{index}", json=kwargs).json() + + def merge_pull(self, owner: str, repo: str, index: int, + merge_message: str = "", merge_style: str = "merge") -> Dict: + """合并 Pull Request""" + data = { + "merge_commit_message": merge_message, + "merge_style": merge_style + } + return self._request("POST", f"/repos/{owner}/{repo}/pulls/{index}/merge", json=data).json() + + def list_pull_comments(self, owner: str, repo: str, index: int) -> List[Dict]: + """列出 Pull Request 的评论""" + return self._request("GET", f"/repos/{owner}/{repo}/issues/{index}/comments").json() + + # ==================== Webhook 操作 ==================== + + def list_hooks(self, owner: str, repo: str) -> List[Dict]: + """列出仓库的 Webhook""" + return self._request("GET", f"/repos/{owner}/{repo}/hooks").json() + + def create_hook(self, owner: str, repo: str, config: Dict, events: List[str], + active: bool = True) -> Dict: + """创建 Webhook""" + data = { + "type": "gitea", + "config": config, + "events": events, + "active": active + } + return self._request("POST", f"/repos/{owner}/{repo}/hooks", json=data).json() + + def delete_hook(self, owner: str, repo: str, hook_id: int) -> None: + """删除 Webhook""" + self._request("DELETE", f"/repos/{owner}/{repo}/hooks/{hook_id}") + + # ==================== Deploy Key 操作 ==================== + + def list_deploy_keys(self, owner: str, repo: str) -> List[Dict]: + """列出仓库的 Deploy Key""" + return self._request("GET", f"/repos/{owner}/{repo}/keys").json() + + def create_deploy_key(self, owner: str, repo: str, title: str, key: str, + read_only: bool = True) -> Dict: + """创建 Deploy Key""" + data = { + "title": title, + "key": key, + "read_only": read_only + } + return self._request("POST", f"/repos/{owner}/{repo}/keys", json=data).json() + + def delete_deploy_key(self, owner: str, repo: str, key_id: int) -> None: + """删除 Deploy Key""" + self._request("DELETE", f"/repos/{owner}/{repo}/keys/{key_id}") + + # ==================== 标签操作 ==================== + + def list_tags(self, owner: str, repo: str, page: int = 1, limit: int = 50) -> List[Dict]: + """列出仓库的标签""" + return self._request("GET", f"/repos/{owner}/{repo}/tags", + params={"page": page, "limit": limit}).json() + + def create_tag(self, owner: str, repo: str, tag_name: str, target: str, + message: str = "", **kwargs) -> Dict: + """创建标签""" + data = { + "tag_name": tag_name, + "target": target, + "message": message + } + data.update(kwargs) + return self._request("POST", f"/repos/{owner}/{repo}/tags", json=data).json() + + # ==================== 搜索操作 ==================== + + def search_repos(self, query: str, page: int = 1, limit: int = 50, **kwargs) -> List[Dict]: + """搜索仓库""" + params = {"q": query, "page": page, "limit": limit} + params.update(kwargs) + return self._request("GET", "/repos/search", params=params).json().get("data", []) + + def search_issues(self, owner: str, repo: str, query: str, + page: int = 1, limit: int = 50) -> List[Dict]: + """搜索 Issue""" + params = {"q": query, "page": page, "limit": limit} + return self._request("GET", f"/repos/{owner}/{repo}/issues/search", params=params).json() + + # ==================== 团队操作 ==================== + + def list_org_teams(self, org: str) -> List[Dict]: + """列出组织的团队""" + return self._request("GET", f"/orgs/{org}/teams").json() + + def create_team(self, org: str, name: str, permission: str = "read", **kwargs) -> Dict: + """创建团队""" + data = {"name": name, "permission": permission} + data.update(kwargs) + return self._request("POST", f"/orgs/{org}/teams", json=data).json() + + def add_team_member(self, org: str, team: str, username: str) -> None: + """添加团队成员""" + self._request("PUT", f"/orgs/{org}/teams/{team}/members/{username}") + + def remove_team_member(self, org: str, team: str, username: str) -> None: + """移除团队成员""" + self._request("DELETE", f"/orgs/{org}/teams/{team}/members/{username}") + + def add_team_repo(self, org: str, team: str, owner: str, repo: str) -> None: + """添加团队仓库""" + self._request("PUT", f"/orgs/{org}/teams/{team}/repos/{owner}/{repo}") + + def remove_team_repo(self, org: str, team: str, owner: str, repo: str) -> None: + """移除团队仓库""" + self._request("DELETE", f"/orgs/{org}/teams/{team}/repos/{owner}/{repo}") + + +def main(): + """命令行入口""" + import argparse + + parser = argparse.ArgumentParser(description="Gitea API Client") + parser.add_argument("--url", default=None, help="Gitea URL") + parser.add_argument("--token", default=None, help="API Token") + parser.add_argument("command", help="要执行的命令") + parser.add_argument("args", nargs="*", help="命令参数") + + args = parser.parse_args() + + client = GiteaClient(url=args.url, token=args.token) + + if args.command == "list-repos": + repos = client.list_my_repos() + for r in repos: + print(f"{r['full_name']}: {r.get('description', '')}") + + elif args.command == "get-repo": + owner, repo = args.args[0].split("/") + r = client.get_repo(owner, repo) + print(json.dumps(r, indent=2)) + + elif args.command == "create-repo": + name = args.args[0] + description = args.args[1] if len(args.args) > 1 else "" + r = client.create_repo(name, description) + print(json.dumps(r, indent=2)) + + elif args.command == "delete-repo": + owner, repo = args.args[0].split("/") + client.delete_repo(owner, repo) + print(f"Repository {owner}/{repo} deleted") + + else: + print(f"Unknown command: {args.command}") + parser.print_help() + + +if __name__ == "__main__": + main()