PYAE1994 commited on
Commit
04b77b0
·
verified ·
1 Parent(s): 9f88063

feat: GOD MODE+ v4.0 - api/routes/github.py

Browse files
Files changed (1) hide show
  1. api/routes/github.py +5 -332
api/routes/github.py CHANGED
@@ -1,336 +1,9 @@
1
  """
2
- GitHub Autonomous Engineering API Routes
3
- Clone, commit, push, PR, issues — all autonomous
4
  """
5
-
6
- import os
7
- import time
8
- import asyncio
9
- import tempfile
10
- import shutil
11
- from typing import Optional
12
-
13
- import httpx
14
- from fastapi import APIRouter, HTTPException, Request
15
-
16
- from core.models import (
17
- GitHubCloneRequest, GitHubCreateRepoRequest,
18
- GitHubCommitRequest, GitHubPRRequest, GitHubIssueRequest,
19
- )
20
- from memory.db import save_memory
21
-
22
  router = APIRouter()
23
 
24
- GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
25
- GITHUB_OWNER = os.environ.get("GITHUB_OWNER", "")
26
- GITHUB_API = "https://api.github.com"
27
-
28
-
29
- def gh_headers():
30
- if not GITHUB_TOKEN:
31
- raise HTTPException(status_code=400, detail="GITHUB_TOKEN not configured")
32
- return {
33
- "Authorization": f"Bearer {GITHUB_TOKEN}",
34
- "Accept": "application/vnd.github+json",
35
- "X-GitHub-Api-Version": "2022-11-28",
36
- }
37
-
38
-
39
- async def gh_get(path: str) -> dict:
40
- async with httpx.AsyncClient(timeout=30) as client:
41
- r = await client.get(f"{GITHUB_API}{path}", headers=gh_headers())
42
- r.raise_for_status()
43
- return r.json()
44
-
45
-
46
- async def gh_post(path: str, data: dict) -> dict:
47
- async with httpx.AsyncClient(timeout=30) as client:
48
- r = await client.post(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
49
- r.raise_for_status()
50
- return r.json()
51
-
52
-
53
- async def gh_put(path: str, data: dict) -> dict:
54
- async with httpx.AsyncClient(timeout=30) as client:
55
- r = await client.put(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
56
- r.raise_for_status()
57
- return r.json()
58
-
59
-
60
- async def gh_patch(path: str, data: dict) -> dict:
61
- async with httpx.AsyncClient(timeout=30) as client:
62
- r = await client.patch(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
63
- r.raise_for_status()
64
- return r.json()
65
-
66
-
67
- # ─── Clone ────────────────────────────────────────────────────────────────────
68
-
69
- @router.post("/clone", summary="Clone a GitHub repository")
70
- async def clone_repo(req: GitHubCloneRequest):
71
- try:
72
- import git
73
- except ImportError:
74
- raise HTTPException(status_code=500, detail="gitpython not installed")
75
-
76
- local_path = req.local_path or f"/tmp/repos/{req.repo_url.split('/')[-1].replace('.git', '')}"
77
- os.makedirs(local_path, exist_ok=True)
78
-
79
- if GITHUB_TOKEN:
80
- url = req.repo_url.replace("https://", f"https://{GITHUB_TOKEN}@")
81
- else:
82
- url = req.repo_url
83
-
84
- try:
85
- if os.path.exists(os.path.join(local_path, ".git")):
86
- repo = git.Repo(local_path)
87
- repo.remotes.origin.pull()
88
- action = "pulled"
89
- else:
90
- repo = git.Repo.clone_from(url, local_path, branch=req.branch, depth=1)
91
- action = "cloned"
92
-
93
- files = []
94
- for root, dirs, fnames in os.walk(local_path):
95
- dirs[:] = [d for d in dirs if d not in [".git", "node_modules", "__pycache__"]]
96
- for f in fnames[:50]:
97
- files.append(os.path.relpath(os.path.join(root, f), local_path))
98
-
99
- # Save to memory
100
- await save_memory(
101
- content=f"Repo {req.repo_url} cloned to {local_path}. Files: {', '.join(files[:20])}",
102
- memory_type="repo",
103
- key=req.repo_url,
104
- )
105
-
106
- return {
107
- "action": action,
108
- "repo_url": req.repo_url,
109
- "local_path": local_path,
110
- "branch": req.branch,
111
- "files_count": len(files),
112
- "files": files[:30],
113
- }
114
- except Exception as e:
115
- raise HTTPException(status_code=500, detail=f"Clone failed: {str(e)}")
116
-
117
-
118
- # ─── Create Repo ──────────────────────────────────────────────────────────────
119
-
120
- @router.post("/create_repo", summary="Create a new GitHub repository")
121
- async def create_repo(req: GitHubCreateRepoRequest):
122
- data = {
123
- "name": req.name,
124
- "description": req.description,
125
- "private": req.private,
126
- "auto_init": req.auto_init,
127
- }
128
- try:
129
- result = await gh_post("/user/repos", data)
130
- return {
131
- "repo": result["full_name"],
132
- "url": result["html_url"],
133
- "clone_url": result["clone_url"],
134
- "default_branch": result.get("default_branch", "main"),
135
- "private": result["private"],
136
- }
137
- except httpx.HTTPStatusError as e:
138
- raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
139
-
140
-
141
- # ─── Commit Files ──────────────────────────────────────────────────────��──────
142
-
143
- @router.post("/commit", summary="Commit files to a repository")
144
- async def commit_files(req: GitHubCommitRequest):
145
- import base64
146
-
147
- owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
148
- results = []
149
-
150
- for file_path, content in req.files.items():
151
- encoded = base64.b64encode(content.encode()).decode()
152
-
153
- # Get current SHA if file exists
154
- sha = None
155
- try:
156
- existing = await gh_get(f"/repos/{owner_repo}/contents/{file_path}?ref={req.branch}")
157
- sha = existing.get("sha")
158
- except Exception:
159
- pass
160
-
161
- payload = {
162
- "message": req.message,
163
- "content": encoded,
164
- "branch": req.branch,
165
- }
166
- if sha:
167
- payload["sha"] = sha
168
-
169
- try:
170
- result = await gh_put(f"/repos/{owner_repo}/contents/{file_path}", payload)
171
- results.append({"file": file_path, "status": "committed", "sha": result["content"]["sha"]})
172
- except Exception as e:
173
- results.append({"file": file_path, "status": "error", "error": str(e)})
174
-
175
- return {
176
- "repo": owner_repo,
177
- "branch": req.branch,
178
- "message": req.message,
179
- "files": results,
180
- "committed": sum(1 for r in results if r["status"] == "committed"),
181
- }
182
-
183
-
184
- # ─── Push ─────────────────────────────────────────────────────────────────────
185
-
186
- @router.post("/push", summary="Push local changes to remote")
187
- async def push_changes(
188
- repo_path: str,
189
- branch: str = "main",
190
- message: str = "Auto-commit by Devin Agent",
191
- ):
192
- try:
193
- import git
194
- repo = git.Repo(repo_path)
195
- repo.git.add(A=True)
196
- if repo.index.diff("HEAD") or repo.untracked_files:
197
- repo.index.commit(message)
198
- origin = repo.remote("origin")
199
- origin.push(refspec=f"HEAD:{branch}")
200
- return {"status": "pushed", "branch": branch, "message": message}
201
- except Exception as e:
202
- raise HTTPException(status_code=500, detail=f"Push failed: {str(e)}")
203
-
204
-
205
- # ─── Create PR ────────────────────────────────────────────────────────────────
206
-
207
- @router.post("/pr/create", summary="Create a Pull Request")
208
- async def create_pr(req: GitHubPRRequest):
209
- owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
210
- data = {
211
- "title": req.title,
212
- "body": req.body,
213
- "head": req.head,
214
- "base": req.base,
215
- "draft": req.draft,
216
- }
217
- try:
218
- result = await gh_post(f"/repos/{owner_repo}/pulls", data)
219
- return {
220
- "pr_number": result["number"],
221
- "title": result["title"],
222
- "url": result["html_url"],
223
- "state": result["state"],
224
- "head": req.head,
225
- "base": req.base,
226
- }
227
- except httpx.HTTPStatusError as e:
228
- raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
229
-
230
-
231
- # ─── Create Issue ─────────────────────────────────────────────────────────────
232
-
233
- @router.post("/issues/create", summary="Create a GitHub Issue")
234
- async def create_issue(req: GitHubIssueRequest):
235
- owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
236
- data = {"title": req.title, "body": req.body, "labels": req.labels}
237
- try:
238
- result = await gh_post(f"/repos/{owner_repo}/issues", data)
239
- return {
240
- "issue_number": result["number"],
241
- "title": result["title"],
242
- "url": result["html_url"],
243
- "state": result["state"],
244
- }
245
- except httpx.HTTPStatusError as e:
246
- raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
247
-
248
-
249
- # ─── Code Review ──────────────────────────────────────────────────────────────
250
-
251
- @router.post("/review", summary="AI code review for a PR")
252
- async def review_pr(repo: str, pr_number: int, request: Request):
253
- owner_repo = repo if "/" in repo else f"{GITHUB_OWNER}/{repo}"
254
- try:
255
- pr = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}")
256
- files = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}/files")
257
-
258
- file_changes = []
259
- for f in files[:10]:
260
- file_changes.append(f"{f['filename']}: +{f.get('additions',0)}/-{f.get('deletions',0)}")
261
-
262
- ws = request.app.state.ws_manager
263
- from core.agent import AgentCore
264
- agent = AgentCore(ws)
265
-
266
- review_prompt = (
267
- f"Review this Pull Request:\n"
268
- f"Title: {pr['title']}\n"
269
- f"Description: {pr.get('body', 'No description')}\n"
270
- f"Files changed: {chr(10).join(file_changes)}\n\n"
271
- f"Provide a constructive code review with: summary, potential issues, suggestions, and verdict."
272
- )
273
- messages = [
274
- {"role": "system", "content": "You are a senior software engineer doing code review. Be constructive, specific, and helpful."},
275
- {"role": "user", "content": review_prompt},
276
- ]
277
- review = await agent.llm_stream(messages)
278
-
279
- # Post review comment
280
- if GITHUB_TOKEN:
281
- await gh_post(f"/repos/{owner_repo}/issues/{pr_number}/comments", {"body": f"🤖 **Devin Agent Code Review**\n\n{review}"})
282
-
283
- return {
284
- "pr_number": pr_number,
285
- "title": pr["title"],
286
- "review": review,
287
- "files_reviewed": len(files),
288
- "posted_to_github": bool(GITHUB_TOKEN),
289
- }
290
- except Exception as e:
291
- raise HTTPException(status_code=500, detail=str(e))
292
-
293
-
294
- # ─── Repo Info ────────────────────────────────────────────────────────────────
295
-
296
- @router.get("/repo/{owner}/{repo}", summary="Get repository info")
297
- async def get_repo_info(owner: str, repo: str):
298
- try:
299
- info = await gh_get(f"/repos/{owner}/{repo}")
300
- return {
301
- "name": info["name"],
302
- "full_name": info["full_name"],
303
- "description": info.get("description"),
304
- "url": info["html_url"],
305
- "default_branch": info["default_branch"],
306
- "language": info.get("language"),
307
- "stars": info["stargazers_count"],
308
- "forks": info["forks_count"],
309
- "open_issues": info["open_issues_count"],
310
- "private": info["private"],
311
- }
312
- except httpx.HTTPStatusError as e:
313
- raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
314
-
315
-
316
- # ─── Status check ─────────────────────────────────────────────────────────────
317
-
318
- @router.get("/status", summary="GitHub integration status")
319
- async def github_status():
320
- configured = bool(GITHUB_TOKEN)
321
- user = None
322
- if configured:
323
- try:
324
- user_info = await gh_get("/user")
325
- user = user_info.get("login")
326
- except Exception:
327
- configured = False
328
- return {
329
- "configured": configured,
330
- "user": user,
331
- "owner": GITHUB_OWNER or user,
332
- "capabilities": [
333
- "clone", "create_repo", "commit", "push",
334
- "pr/create", "issues/create", "review"
335
- ],
336
- }
 
1
  """
2
+ GitHub API Routes (legacy compat + new)
 
3
  """
4
+ from fastapi import APIRouter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  router = APIRouter()
6
 
7
+ @router.get("/info")
8
+ async def github_info():
9
+ return {"status": "GitHub routes active — use /api/v1/exec/github/* for full ops"}