diff --git a/homelab-ai-bot/context.py b/homelab-ai-bot/context.py index 22788208..f7269bed 100644 --- a/homelab-ai-bot/context.py +++ b/homelab-ai-bot/context.py @@ -148,6 +148,24 @@ def _tool_get_wordpress_stats() -> str: return wordpress_client.format_overview(cfg) +def _tool_create_issue(title: str, body: str = "") -> str: + cfg = _load_config() + forgejo_client.init(cfg) + result = forgejo_client.create_issue(title, body) + if "error" in result: + return result["error"] + return f"Issue #{result['number']} erstellt: {result['title']}" + + +def _tool_close_issue(number: int) -> str: + cfg = _load_config() + forgejo_client.init(cfg) + result = forgejo_client.close_issue(int(number)) + if "error" in result: + return result["error"] + return f"Issue #{result['number']} geschlossen: {result['title']}" + + def _tool_get_forgejo_status() -> str: cfg = _load_config() forgejo_client.init(cfg) @@ -199,6 +217,8 @@ def get_tool_handlers() -> dict: "get_wordpress_stats": lambda: _tool_get_wordpress_stats(), "get_feed_stats": lambda: _tool_get_feed_stats(), "get_forgejo_status": lambda: _tool_get_forgejo_status(), + "create_issue": lambda title, body="": _tool_create_issue(title, body), + "close_issue": lambda number: _tool_close_issue(number), "get_seafile_status": lambda: _tool_get_seafile_status(), "get_backup_status": lambda: _tool_get_backup_status(), } diff --git a/homelab-ai-bot/core/forgejo_client.py b/homelab-ai-bot/core/forgejo_client.py index 3353db9e..48b55a23 100644 --- a/homelab-ai-bot/core/forgejo_client.py +++ b/homelab-ai-bot/core/forgejo_client.py @@ -64,6 +64,52 @@ def get_recent_commits(repo: str = REPO, limit: int = 10) -> list[dict]: return results +def _post(endpoint: str, data: dict) -> Optional[dict]: + if not FORGEJO_URL or not FORGEJO_TOKEN: + return None + try: + r = requests.post( + f"{FORGEJO_URL}{endpoint}", + headers={"Authorization": f"token {FORGEJO_TOKEN}", "Content-Type": "application/json"}, + json=data, + timeout=10, + ) + r.raise_for_status() + return r.json() + except Exception: + return None + + +def _patch(endpoint: str, data: dict) -> Optional[dict]: + if not FORGEJO_URL or not FORGEJO_TOKEN: + return None + try: + r = requests.patch( + f"{FORGEJO_URL}{endpoint}", + headers={"Authorization": f"token {FORGEJO_TOKEN}", "Content-Type": "application/json"}, + json=data, + timeout=10, + ) + r.raise_for_status() + return r.json() + except Exception: + return None + + +def create_issue(title: str, body: str = "", repo: str = REPO) -> dict: + result = _post(f"/repos/{repo}/issues", {"title": title, "body": body}) + if result: + return {"number": result["number"], "title": result["title"], "url": result["html_url"]} + return {"error": "Issue konnte nicht erstellt werden"} + + +def close_issue(number: int, repo: str = REPO) -> dict: + result = _patch(f"/repos/{repo}/issues/{number}", {"state": "closed"}) + if result: + return {"number": result["number"], "title": result["title"], "state": "closed"} + return {"error": f"Issue #{number} konnte nicht geschlossen werden"} + + def get_repos() -> list[dict]: data = _get("/repos/search", {"limit": 20}) if not data or "data" not in data: diff --git a/homelab-ai-bot/llm.py b/homelab-ai-bot/llm.py index cd21ae5a..3efe0a03 100644 --- a/homelab-ai-bot/llm.py +++ b/homelab-ai-bot/llm.py @@ -130,6 +130,35 @@ TOOLS = [ "parameters": {"type": "object", "properties": {}, "required": []}, }, }, + { + "type": "function", + "function": { + "name": "create_issue", + "description": "Neues TODO/Issue in Forgejo erstellen", + "parameters": { + "type": "object", + "properties": { + "title": {"type": "string", "description": "Titel des Issues"}, + "body": {"type": "string", "description": "Beschreibung (optional)", "default": ""}, + }, + "required": ["title"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "close_issue", + "description": "Ein bestehendes Issue/TODO in Forgejo schliessen (als erledigt markieren)", + "parameters": { + "type": "object", + "properties": { + "number": {"type": "integer", "description": "Issue-Nummer (z.B. 3)"}, + }, + "required": ["number"], + }, + }, + }, { "type": "function", "function": {