Viraj77 commited on
Commit
80a6b2f
·
verified ·
1 Parent(s): 91136d4

Upload 7 files

Browse files
Files changed (7) hide show
  1. .gitignore +51 -0
  2. README.md +171 -0
  3. ai_helper.py +162 -0
  4. app.py +638 -0
  5. ghclient.py +225 -0
  6. requirements.txt +4 -0
  7. test_fibonacci.py +13 -0
.gitignore ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ env/
8
+ venv/
9
+ ENV/
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # Virtual environments
27
+ venv/
28
+ ENV/
29
+ env/
30
+
31
+ # IDE
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+ *~
37
+
38
+ # OS
39
+ .DS_Store
40
+ Thumbs.db
41
+
42
+ # Environment variables
43
+ .env
44
+ .env.local
45
+
46
+ # Gradio
47
+ gradio_cached_examples/
48
+ flagged/
49
+
50
+ # Logs
51
+ *.log
README.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: GitNexus
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 4.0.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # 🚀 GitNexus
13
+
14
+ **MCP-Enabled GitHub Automation for AI Agents**
15
+
16
+ ## 👥 Project Team
17
+
18
+ | Name | Hugging Face Profile |
19
+ |------|----------------------|
20
+ | **Viraj Talathi** | [@Viraj77](https://huggingface.co/Viraj77) |
21
+ | **Pranjal Prakash** | [@pranjal00](https://huggingface.co/pranjal00) |
22
+ | **Karan Singh** | [@karansingh99](https://huggingface.co/karansingh99) |
23
+ | **Shriprasasd Patil** | [@Shriprasad-P](https://huggingface.co/Shriprasad-P) |
24
+ | **Bharath Nuthalapati** | [@bharath-nuthalapati](https://huggingface.co/bharath-nuthalapati) |
25
+
26
+ This Hugging Face Space demonstrates GitHub automation using MCP (Model Context Protocol) tools integrated with a Gradio UI. AI agents like Claude, Cursor, and Antigravity can discover and call GitHub functions via MCP, while humans can use the intuitive web interface.
27
+
28
+ ## 🎯 Features
29
+
30
+ ### GitHub Operations
31
+ - **Create Repository** - Create new public/private GitHub repositories
32
+ - **List Repositories** - View all repositories for the authenticated account
33
+ - **Create Issue** - Open issues in any repository
34
+ - **List Issues** - View issues filtered by state (open/closed/all)
35
+ - **Commit File** - Create or update files in repositories
36
+ - **Read File** - Read file contents from repositories
37
+
38
+ ### MCP Tools for AI Agents
39
+ The following tools are exposed via MCP for AI agent automation:
40
+ - `github.create_repo`
41
+ - `github.list_repos`
42
+ - `github.create_issue`
43
+ - `github.list_issues`
44
+ - `github.commit_file`
45
+ - `github.read_file`
46
+ - `github.generate_docs_with_tts`
47
+
48
+ ## 🔧 Tech Stack
49
+
50
+ - **Python** - Core language
51
+ - **Gradio** - Web UI framework with MCP server support
52
+ - **PyGithub** - GitHub API wrapper
53
+ - **MCP** - Model Context Protocol for AI agent integration
54
+ - **Gemini 2.5 Flash** - AI Code Analysis & Documentation
55
+ - **ElevenLabs** - Text-to-Speech Generation
56
+
57
+ ## 📦 Project Structure
58
+
59
+ ```
60
+ HF-Pro/
61
+ ├── app.py # Main Gradio application with MCP handlers
62
+ ├── ghclient.py # GitHub API client wrapper
63
+ ├── ai_helper.py # AI Documentation & TTS helper
64
+ ├── requirements.txt # Python dependencies
65
+ └── README.md # This file
66
+ ```
67
+
68
+ ## 🚀 Deployment to Hugging Face Spaces
69
+
70
+ ### Prerequisites
71
+ 1. A Hugging Face account
72
+ 2. A GitHub Personal Access Token (PAT) with repo permissions
73
+ 3. A Google Gemini API Key
74
+ 4. An ElevenLabs API Key
75
+
76
+ ### Steps
77
+
78
+ 1. **Create a new Space on Hugging Face**
79
+ - Go to https://huggingface.co/new-space
80
+ - Choose a name for your Space
81
+ - Select **Gradio** as the SDK
82
+ - Choose **Public** or **Private** visibility
83
+ - Click "Create Space"
84
+
85
+ 2. **Upload the files**
86
+ - Upload all 5 files from this project:
87
+ - `app.py`
88
+ - `ghclient.py`
89
+ - `ai_helper.py`
90
+ - `requirements.txt`
91
+ - `README.md`
92
+
93
+ 3. **Configure Secrets**
94
+ - Go to your Space settings
95
+ - Navigate to "Repository secrets"
96
+ - Add the following secrets:
97
+ - `GITHUB_TOKEN`: Your GitHub Personal Access Token
98
+ - `GEMINI_API_KEY`: Your Google Gemini API Key
99
+ - `ELEVENLABS_API_KEY`: Your ElevenLabs API Key
100
+
101
+ 4. **Wait for deployment**
102
+ - Hugging Face will automatically build and deploy your Space
103
+ - Once ready, you'll see the Gradio interface
104
+
105
+ ## 🎮 Usage
106
+
107
+ ### For Humans (Web UI)
108
+
109
+ The Gradio interface has 3 tabs:
110
+
111
+ **📦 Repositories Tab**
112
+ - Create new repositories with name, privacy setting, and description
113
+ - List all repositories for the authenticated account
114
+
115
+ **📋 Issues Tab**
116
+ - Create issues in any repository (format: `username/repo`)
117
+ - List issues filtered by state (open/closed/all)
118
+
119
+ **📄 Custom Files Tab**
120
+ - Commit files to repositories (creates or updates)
121
+ - Read file contents from repositories
122
+
123
+ **🤖 Code Documentation Tab**
124
+ - Upload code files for AI analysis
125
+ - Generate comprehensive documentation
126
+ - Listen to 2-line audio summary
127
+ - Commit documentation to GitHub
128
+
129
+ ## 🔒 Security Notes
130
+
131
+ - **No user authentication required** - The Space uses a server-side GitHub token
132
+ - **All repos are public by default** - Anyone can view created repositories
133
+ - **Repository name validation** - Input validation prevents malicious repo names
134
+ - **Token security** - GitHub token is stored securely in HF Spaces secrets (never in code)
135
+
136
+ ## 🛠️ Local Development
137
+
138
+ To run locally:
139
+
140
+ 1. **Clone the repository**
141
+ ```bash
142
+ git clone <your-space-url>
143
+ cd HF-Pro
144
+ ```
145
+
146
+ 2. **Install dependencies**
147
+ ```bash
148
+ pip install -r requirements.txt
149
+ ```
150
+
151
+ 3. **Set environment variables**
152
+ ```bash
153
+ # Windows (PowerShell)
154
+ $env:GITHUB_TOKEN="your_github_token"
155
+ $env:GEMINI_API_KEY="your_gemini_key"
156
+ $env:ELEVENLABS_API_KEY="your_elevenlabs_key"
157
+ ```
158
+
159
+ 4. **Run the application**
160
+ ```bash
161
+ python app.py
162
+ ```
163
+
164
+ 5. **Open in browser**
165
+ - Navigate to `http://localhost:7860`
166
+
167
+ ## 📄 License
168
+
169
+ MIT License - feel free to use this code for your own projects.
170
+
171
+ **Built with ❤️ for AI agent automation**
ai_helper.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AI Helper Module
3
+ Handles AI-powered documentation generation and text-to-speech
4
+ """
5
+
6
+ import os
7
+ import google.generativeai as genai
8
+ from elevenlabs.client import ElevenLabs
9
+ from typing import Dict, Tuple
10
+ import re
11
+
12
+
13
+ class AIDocumentationGenerator:
14
+ """AI-powered code documentation generator with TTS"""
15
+
16
+ def __init__(self, user_gemini_key: str = None):
17
+ """
18
+ Initialize AI services with API keys
19
+
20
+ Args:
21
+ user_gemini_key: Optional user-provided Gemini API key
22
+ """
23
+ # Initialize Gemini - prioritize user key, then env var
24
+ self.gemini_key = user_gemini_key or os.environ.get("GEMINI_API_KEY")
25
+
26
+ if self.gemini_key:
27
+ genai.configure(api_key=self.gemini_key)
28
+ self.gemini_model = genai.GenerativeModel('gemini-2.5-flash')
29
+ else:
30
+ self.gemini_model = None
31
+
32
+ # Initialize ElevenLabs
33
+ self.elevenlabs_key = os.environ.get("ELEVENLABS_API_KEY")
34
+ if self.elevenlabs_key:
35
+ self.elevenlabs_client = ElevenLabs(api_key=self.elevenlabs_key)
36
+ else:
37
+ self.elevenlabs_client = None
38
+
39
+ def generate_documentation(self, code_content: str, language: str, filename: str) -> Dict[str, str]:
40
+ """
41
+ Generate comprehensive documentation from code using Gemini
42
+
43
+ Args:
44
+ code_content: The source code to analyze
45
+ language: Programming language (python, javascript, etc.)
46
+ filename: Original filename
47
+
48
+ Returns:
49
+ Dict with 'documentation' and 'summary' keys
50
+ """
51
+ if not self.gemini_model:
52
+ raise ValueError("Gemini API key not configured. Set GEMINI_API_KEY environment variable.")
53
+
54
+ # Create prompt for Gemini
55
+ prompt = f"""Analyze this {language} code from file "{filename}":
56
+
57
+ ```{language}
58
+ {code_content}
59
+ ```
60
+
61
+ Generate comprehensive documentation in README markdown format with these sections:
62
+ 1. **Overview** - What this code does (2-3 sentences)
63
+ 2. **Key Components** - Main functions/classes with brief descriptions
64
+ 3. **Usage Examples** - How to use this code (with code blocks)
65
+ 4. **Dependencies** - Required libraries/packages
66
+ 5. **Installation** - Setup instructions
67
+
68
+ Then provide a STRICT 2-LINE SUMMARY (maximum 2 sentences, around 150-200 characters total) that captures the essence of this code.
69
+
70
+ Format your response EXACTLY like this:
71
+
72
+ ## Documentation
73
+
74
+ [Full documentation here in markdown format]
75
+
76
+ ## Summary
77
+
78
+ [Exactly 2 lines/sentences here - be concise and clear]
79
+ """
80
+
81
+ try:
82
+ # Generate content with Gemini
83
+ response = self.gemini_model.generate_content(prompt)
84
+ full_response = response.text
85
+
86
+ # Parse documentation and summary
87
+ parts = full_response.split("## Summary")
88
+
89
+ if len(parts) == 2:
90
+ documentation = parts[0].replace("## Documentation", "").strip()
91
+ summary = parts[1].strip()
92
+
93
+ # Ensure summary is max 2 lines
94
+ summary_lines = [line.strip() for line in summary.split('\n') if line.strip()]
95
+ summary = ' '.join(summary_lines[:2])
96
+ else:
97
+ # Fallback if format is not as expected
98
+ documentation = full_response
99
+ summary = self._extract_summary_fallback(full_response)
100
+
101
+ return {
102
+ "documentation": documentation,
103
+ "summary": summary
104
+ }
105
+
106
+ except Exception as e:
107
+ raise Exception(f"Failed to generate documentation: {str(e)}")
108
+
109
+ def _extract_summary_fallback(self, text: str) -> str:
110
+ """Extract a 2-line summary if the format is not as expected"""
111
+ # Take first 2 sentences
112
+ sentences = re.split(r'[.!?]+', text)
113
+ sentences = [s.strip() for s in sentences if s.strip()]
114
+ return '. '.join(sentences[:2]) + '.'
115
+
116
+ def text_to_speech(self, text: str) -> bytes:
117
+ """
118
+ Convert text to speech using ElevenLabs
119
+
120
+ Args:
121
+ text: Text to convert to speech
122
+
123
+ Returns:
124
+ Audio bytes (MP3 format)
125
+ """
126
+ if not self.elevenlabs_client:
127
+ raise ValueError("ElevenLabs API key not configured. Set ELEVENLABS_API_KEY environment variable.")
128
+
129
+ try:
130
+ # Generate audio with professional voice using new API
131
+ audio_generator = self.elevenlabs_client.text_to_speech.convert(
132
+ text=text,
133
+ voice_id="EXAVITQu4vr4xnSDxMaL", # Rachel voice ID
134
+ model_id="eleven_monolingual_v1"
135
+ )
136
+
137
+ # Collect audio bytes
138
+ audio_bytes = b"".join(audio_generator)
139
+ return audio_bytes
140
+
141
+ except Exception as e:
142
+ raise Exception(f"Failed to generate audio: {str(e)}")
143
+
144
+ def process_file(self, file_content: str, language: str, filename: str) -> Tuple[str, str, bytes]:
145
+ """
146
+ Complete pipeline: analyze code, generate docs, create TTS
147
+
148
+ Args:
149
+ file_content: Code content
150
+ language: Programming language
151
+ filename: Original filename
152
+
153
+ Returns:
154
+ Tuple of (documentation, summary, audio_bytes)
155
+ """
156
+ # Generate documentation and summary
157
+ result = self.generate_documentation(file_content, language, filename)
158
+
159
+ # Generate audio from summary
160
+ audio = self.text_to_speech(result["summary"])
161
+
162
+ return result["documentation"], result["summary"], audio
app.py ADDED
@@ -0,0 +1,638 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GitHub Automation Space - MCP-Enabled Gradio Application
3
+ Demonstrates GitHub automation with MCP tool discovery for AI agents
4
+ """
5
+
6
+ import gradio as gr
7
+ import os
8
+ from ghclient import GitHubClient
9
+ from ai_helper import AIDocumentationGenerator
10
+ from typing import Dict, List
11
+
12
+
13
+ # Initialize GitHub client
14
+ def get_github_client(token: str = None):
15
+ """Get or create GitHub client instance"""
16
+ try:
17
+ return GitHubClient(token)
18
+ except ValueError as e:
19
+ return None
20
+
21
+
22
+ # MCP Handler Functions
23
+ def mcp_create_repo(name: str, description: str = "", token: str = None) -> Dict:
24
+ """MCP handler for creating a GitHub repository (always public)"""
25
+ client = get_github_client(token)
26
+ if not client:
27
+ return {"error": "GitHub token not configured"}
28
+ return client.create_repo(name, private=False, description=description)
29
+
30
+
31
+ def mcp_list_repos(token: str = None) -> List[Dict]:
32
+ """MCP handler for listing GitHub repositories"""
33
+ client = get_github_client(token)
34
+ if not client:
35
+ return [{"error": "GitHub token not configured"}]
36
+ return client.list_repos()
37
+
38
+
39
+ def mcp_create_issue(repo_full_name: str, title: str, body: str = "", token: str = None) -> Dict:
40
+ """MCP handler for creating a GitHub issue"""
41
+ client = get_github_client(token)
42
+ if not client:
43
+ return {"error": "GitHub token not configured"}
44
+ return client.create_issue(repo_full_name, title, body)
45
+
46
+
47
+ def mcp_list_issues(repo_full_name: str, state: str = "open", token: str = None) -> List[Dict]:
48
+ """MCP handler for listing GitHub issues"""
49
+ client = get_github_client(token)
50
+ if not client:
51
+ return [{"error": "GitHub token not configured"}]
52
+ return client.list_issues(repo_full_name, state)
53
+
54
+
55
+ def mcp_commit_file(repo_full_name: str, path: str, content: str, message: str, token: str = None) -> Dict:
56
+ """MCP handler for committing a file to GitHub"""
57
+ client = get_github_client(token)
58
+ if not client:
59
+ return {"error": "GitHub token not configured"}
60
+ return client.commit_file(repo_full_name, path, content, message)
61
+
62
+
63
+ def mcp_read_file(repo_full_name: str, path: str, token: str = None) -> Dict:
64
+ """MCP handler for reading a file from GitHub"""
65
+ client = get_github_client(token)
66
+ if not client:
67
+ return {"error": "GitHub token not configured"}
68
+ return client.read_file(repo_full_name, path)
69
+
70
+
71
+ # Initialize AI helper
72
+ def get_ai_helper(user_gemini_key: str = None):
73
+ """Get or create AI documentation generator instance"""
74
+ try:
75
+ return AIDocumentationGenerator(user_gemini_key)
76
+ except ValueError as e:
77
+ return None
78
+
79
+
80
+ def mcp_generate_docs_with_tts(code_content: str, language: str, filename: str) -> Dict:
81
+ """MCP handler for generating documentation with TTS"""
82
+ ai_helper = get_ai_helper()
83
+ if not ai_helper:
84
+ return {"error": "AI services not configured. Set GEMINI_API_KEY and ELEVENLABS_API_KEY."}
85
+
86
+ try:
87
+ result = ai_helper.generate_documentation(code_content, language, filename)
88
+ audio = ai_helper.text_to_speech(result["summary"])
89
+
90
+ return {
91
+ "documentation": result["documentation"],
92
+ "summary": result["summary"],
93
+ "audio_generated": True,
94
+ "filename": f"{filename}_summary.mp3"
95
+ }
96
+ except Exception as e:
97
+ return {"error": str(e)}
98
+
99
+
100
+ # MCP Handlers Configuration
101
+ mcp_handlers = {
102
+ "github.create_repo": {
103
+ "handler": mcp_create_repo,
104
+ "description": "Create a new GitHub repository (always public)",
105
+ "parameters": {
106
+ "type": "object",
107
+ "properties": {
108
+ "name": {
109
+ "type": "string",
110
+ "description": "Repository name"
111
+ },
112
+ "description": {
113
+ "type": "string",
114
+ "description": "Repository description",
115
+ "default": ""
116
+ }
117
+ },
118
+ "required": ["name"]
119
+ }
120
+ },
121
+ "github.list_repos": {
122
+ "handler": mcp_list_repos,
123
+ "description": "List all repositories for the authenticated user",
124
+ "parameters": {
125
+ "type": "object",
126
+ "properties": {}
127
+ }
128
+ },
129
+ "github.create_issue": {
130
+ "handler": mcp_create_issue,
131
+ "description": "Create an issue in a GitHub repository",
132
+ "parameters": {
133
+ "type": "object",
134
+ "properties": {
135
+ "repo_full_name": {
136
+ "type": "string",
137
+ "description": "Full repository name (e.g., 'username/repo')"
138
+ },
139
+ "title": {
140
+ "type": "string",
141
+ "description": "Issue title"
142
+ },
143
+ "body": {
144
+ "type": "string",
145
+ "description": "Issue body/description",
146
+ "default": ""
147
+ }
148
+ },
149
+ "required": ["repo_full_name", "title"]
150
+ }
151
+ },
152
+ "github.list_issues": {
153
+ "handler": mcp_list_issues,
154
+ "description": "List issues in a GitHub repository",
155
+ "parameters": {
156
+ "type": "object",
157
+ "properties": {
158
+ "repo_full_name": {
159
+ "type": "string",
160
+ "description": "Full repository name (e.g., 'username/repo')"
161
+ },
162
+ "state": {
163
+ "type": "string",
164
+ "description": "Issue state filter: 'open', 'closed', or 'all'",
165
+ "default": "open",
166
+ "enum": ["open", "closed", "all"]
167
+ }
168
+ },
169
+ "required": ["repo_full_name"]
170
+ }
171
+ },
172
+ "github.commit_file": {
173
+ "handler": mcp_commit_file,
174
+ "description": "Create or update a file in a GitHub repository",
175
+ "parameters": {
176
+ "type": "object",
177
+ "properties": {
178
+ "repo_full_name": {
179
+ "type": "string",
180
+ "description": "Full repository name (e.g., 'username/repo')"
181
+ },
182
+ "path": {
183
+ "type": "string",
184
+ "description": "File path in repository"
185
+ },
186
+ "content": {
187
+ "type": "string",
188
+ "description": "File content"
189
+ },
190
+ "message": {
191
+ "type": "string",
192
+ "description": "Commit message"
193
+ }
194
+ },
195
+ "required": ["repo_full_name", "path", "content", "message"]
196
+ }
197
+ },
198
+ "github.read_file": {
199
+ "handler": mcp_read_file,
200
+ "description": "Read a file from a GitHub repository",
201
+ "parameters": {
202
+ "type": "object",
203
+ "properties": {
204
+ "repo_full_name": {
205
+ "type": "string",
206
+ "description": "Full repository name (e.g., 'username/repo')"
207
+ },
208
+ "path": {
209
+ "type": "string",
210
+ "description": "File path in repository"
211
+ }
212
+ },
213
+ "required": ["repo_full_name", "path"]
214
+ }
215
+ },
216
+ "github.generate_docs_with_tts": {
217
+ "handler": mcp_generate_docs_with_tts,
218
+ "description": "Generate code documentation and audio summary using AI (Gemini + ElevenLabs)",
219
+ "parameters": {
220
+ "type": "object",
221
+ "properties": {
222
+ "code_content": {
223
+ "type": "string",
224
+ "description": "The code to analyze and document"
225
+ },
226
+ "language": {
227
+ "type": "string",
228
+ "description": "Programming language (python, javascript, java, cpp, go, rust, typescript, etc.)"
229
+ },
230
+ "filename": {
231
+ "type": "string",
232
+ "description": "Original filename for context"
233
+ }
234
+ },
235
+ "required": ["code_content", "language", "filename"]
236
+ }
237
+ }
238
+ }
239
+
240
+
241
+ # UI Handler Functions
242
+ def ui_create_repo(name: str, description: str, token: str = "") -> str:
243
+ """UI handler for creating a repository"""
244
+ try:
245
+ result = mcp_create_repo(name, description, token if token.strip() else None)
246
+ if "error" in result:
247
+ return f"❌ Error: {result['error']}"
248
+ return f"✅ Repository created!\n\n📦 Name: {result['name']}\n🔗 URL: {result['url']}\n📝 Description: {result.get('description', 'N/A')}"
249
+ except Exception as e:
250
+ return f"❌ Error: {str(e)}"
251
+
252
+
253
+ def ui_list_repos(token: str = "") -> str:
254
+ """UI handler for listing repositories"""
255
+ try:
256
+ repos = mcp_list_repos(token if token.strip() else None)
257
+ if not repos:
258
+ return "No repositories found."
259
+ if isinstance(repos, list) and len(repos) > 0 and "error" in repos[0]:
260
+ return f"❌ Error: {repos[0]['error']}"
261
+
262
+ output = f"📚 Found {len(repos)} repositories:\n\n"
263
+ for repo in repos:
264
+ output += f"📦 {repo['name']}\n"
265
+ output += f" 🔗 {repo['url']}\n"
266
+ output += f" 🔒 {'Private' if repo['private'] else 'Public'}\n"
267
+ if repo.get('description'):
268
+ output += f" 📝 {repo['description']}\n"
269
+ output += "\n"
270
+ return output
271
+ except Exception as e:
272
+ return f"❌ Error: {str(e)}"
273
+
274
+
275
+ def ui_create_issue(repo_full_name: str, title: str, body: str, token: str = "") -> str:
276
+ """UI handler for creating an issue"""
277
+ try:
278
+ result = mcp_create_issue(repo_full_name, title, body, token if token.strip() else None)
279
+ if "error" in result:
280
+ return f"❌ Error: {result['error']}"
281
+ return f"✅ Issue created!\n\n🔢 Number: #{result['number']}\n📌 Title: {result['title']}\n🔗 URL: {result['url']}\n📊 State: {result['state']}"
282
+ except Exception as e:
283
+ return f"❌ Error: {str(e)}"
284
+
285
+
286
+ def ui_list_issues(repo_full_name: str, state: str, token: str = "") -> str:
287
+ """UI handler for listing issues"""
288
+ try:
289
+ issues = mcp_list_issues(repo_full_name, state, token if token.strip() else None)
290
+ if not issues:
291
+ return f"No {state} issues found."
292
+ if isinstance(issues, list) and len(issues) > 0 and "error" in issues[0]:
293
+ return f"❌ Error: {issues[0]['error']}"
294
+
295
+ output = f"📋 Found {len(issues)} {state} issues:\n\n"
296
+ for issue in issues:
297
+ output += f"🔢 #{issue['number']} - {issue['title']}\n"
298
+ output += f" 🔗 {issue['url']}\n"
299
+ output += f" 📊 {issue['state']}\n\n"
300
+ return output
301
+ except Exception as e:
302
+ return f"❌ Error: {str(e)}"
303
+
304
+
305
+ def ui_commit_file(repo_full_name: str, path: str, content: str, message: str, token: str = "") -> str:
306
+ """UI handler for committing a file"""
307
+ try:
308
+ result = mcp_commit_file(repo_full_name, path, content, message, token if token.strip() else None)
309
+ if "error" in result:
310
+ return f"❌ Error: {result['error']}"
311
+ return f"✅ File {result['action']}!\n\n📄 Path: {result['path']}\n🔗 URL: {result['url']}\n📝 Commit: {result['commit_sha'][:7]}\n⚡ Action: {result['action'].upper()}"
312
+ except Exception as e:
313
+ return f"❌ Error: {str(e)}"
314
+
315
+
316
+ def ui_read_file(repo_full_name: str, path: str, token: str = "") -> tuple:
317
+ """UI handler for reading a file"""
318
+ try:
319
+ result = mcp_read_file(repo_full_name, path, token if token.strip() else None)
320
+ if "error" in result:
321
+ return f"❌ Error: {result['error']}", ""
322
+
323
+ info = f"✅ File read successfully!\n\n📄 Path: {result['path']}\n🔗 URL: {result['url']}\n📊 Size: {result['size']} bytes"
324
+ return info, result['content']
325
+ except Exception as e:
326
+ return f"❌ Error: {str(e)}", ""
327
+
328
+
329
+ def ui_generate_docs(file, language: str, user_key: str = "") -> tuple:
330
+ """UI handler for AI documentation generation with TTS"""
331
+ try:
332
+ if file is None:
333
+ return "❌ Please upload a file", "", None
334
+
335
+ # Read uploaded file
336
+ if hasattr(file, 'name'):
337
+ filename = os.path.basename(file.name)
338
+ with open(file.name, 'r', encoding='utf-8') as f:
339
+ code_content = f.read()
340
+ else:
341
+ return "❌ Invalid file upload", "", None
342
+
343
+ # Generate documentation and TTS
344
+ # Use user key if provided, otherwise fallback to env var
345
+ ai_helper = get_ai_helper(user_key if user_key.strip() else None)
346
+ if not ai_helper or not ai_helper.gemini_model:
347
+ return "❌ AI services not configured. Please set GEMINI_API_KEY (env) or provide your own key.", "", None
348
+
349
+ docs, summary, audio = ai_helper.process_file(code_content, language, filename)
350
+
351
+ # Save audio to temporary file for Gradio
352
+ import tempfile
353
+ audio_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3').name
354
+ with open(audio_path, 'wb') as f:
355
+ f.write(audio)
356
+
357
+ return docs, summary, audio_path
358
+
359
+ except Exception as e:
360
+ return f"❌ Error: {str(e)}", "", None
361
+
362
+
363
+ def ui_commit_docs(repo_full_name: str, path: str, documentation: str, token: str = "") -> str:
364
+ """UI handler for committing generated documentation"""
365
+ try:
366
+ if not documentation or documentation.startswith("❌"):
367
+ return "❌ No documentation to commit. Generate documentation first."
368
+
369
+ result = mcp_commit_file(repo_full_name, path, documentation, "Add AI-generated documentation", token if token.strip() else None)
370
+ if "error" in result:
371
+ return f"❌ Error: {result['error']}"
372
+ return f"✅ Documentation committed!\n\n📄 Path: {result['path']}\n🔗 URL: {result['url']}\n📝 Commit: {result['commit_sha'][:7]}"
373
+ except Exception as e:
374
+ return f"❌ Error: {str(e)}"
375
+
376
+
377
+ def ui_commit_code_file(file, repo_full_name: str, path: str, token: str = "") -> str:
378
+ """UI handler for committing the uploaded code file"""
379
+ try:
380
+ if file is None:
381
+ return "❌ No file uploaded. Please upload a code file first."
382
+
383
+ # Read uploaded file
384
+ if hasattr(file, 'name'):
385
+ with open(file.name, 'r', encoding='utf-8') as f:
386
+ code_content = f.read()
387
+ else:
388
+ return "❌ Invalid file upload"
389
+
390
+ result = mcp_commit_file(repo_full_name, path, code_content, f"Add {os.path.basename(file.name)}", token if token.strip() else None)
391
+ if "error" in result:
392
+ return f"❌ Error: {result['error']}"
393
+ return f"✅ Code file committed!\n\n📄 Path: {result['path']}\n🔗 URL: {result['url']}\n📝 Commit: {result['commit_sha'][:7]}"
394
+ except Exception as e:
395
+ return f"❌ Error: {str(e)}"
396
+
397
+
398
+ def ui_commit_both(file, repo_full_name: str, code_path: str, docs_path: str, documentation: str, token: str = "") -> str:
399
+ """UI handler for committing both code file and documentation"""
400
+ try:
401
+ results = []
402
+ token_val = token if token.strip() else None
403
+
404
+ # Commit code file
405
+ if file is not None:
406
+ if hasattr(file, 'name'):
407
+ with open(file.name, 'r', encoding='utf-8') as f:
408
+ code_content = f.read()
409
+
410
+ result = mcp_commit_file(repo_full_name, code_path, code_content, f"Add {os.path.basename(file.name)}", token_val)
411
+ if "error" in result:
412
+ results.append(f"❌ Code file error: {result['error']}")
413
+ else:
414
+ results.append(f"✅ Code file committed: {result['url']}")
415
+ else:
416
+ results.append("⚠️ No code file uploaded, skipping")
417
+
418
+ # Commit documentation
419
+ if documentation and not documentation.startswith("❌"):
420
+ result = mcp_commit_file(repo_full_name, docs_path, documentation, "Add AI-generated documentation", token_val)
421
+ if "error" in result:
422
+ results.append(f"❌ Documentation error: {result['error']}")
423
+ else:
424
+ results.append(f"✅ Documentation committed: {result['url']}")
425
+ else:
426
+ results.append("⚠️ No documentation generated, skipping")
427
+
428
+ return "\n\n".join(results)
429
+ except Exception as e:
430
+ return f"❌ Error: {str(e)}"
431
+
432
+
433
+ # Build Gradio UI
434
+ def build_ui():
435
+ """Build the Gradio interface"""
436
+
437
+ with gr.Blocks(title="GitNexus") as demo:
438
+ gr.Markdown("""
439
+ # 🚀 GitNexus
440
+
441
+ **MCP-Enabled GitHub Automation for AI Agents**
442
+
443
+ 🔗 **Demo GitHub Account**: [https://github.com/DemoAcc4HF](https://github.com/DemoAcc4HF)
444
+ 👤 **Username**: `DemoAcc4HF`
445
+ """)
446
+
447
+ # Global GitHub Token Input
448
+ with gr.Row():
449
+ user_github_token = gr.Textbox(
450
+ label="Optional: Your GitHub Token (Classic)",
451
+ placeholder="ghp_... (Leave empty to use default system token)",
452
+ type="password"
453
+ )
454
+
455
+ with gr.Tabs():
456
+ # TAB 1: Repositories
457
+ with gr.Tab("📦 Repositories"):
458
+ gr.Markdown("### Create Repository")
459
+ repo_name = gr.Textbox(label="Repository Name", placeholder="my-awesome-repo")
460
+ repo_description = gr.Textbox(label="Description", placeholder="A cool project")
461
+ create_repo_btn = gr.Button("Create Repository", variant="primary")
462
+ create_repo_output = gr.Textbox(label="Result", lines=6)
463
+
464
+ gr.Markdown("### List Repositories")
465
+ list_repos_btn = gr.Button("List All Repositories")
466
+ list_repos_output = gr.Textbox(label="Repositories", lines=10)
467
+
468
+ create_repo_btn.click(
469
+ ui_create_repo,
470
+ inputs=[repo_name, repo_description, user_github_token],
471
+ outputs=create_repo_output
472
+ )
473
+ list_repos_btn.click(
474
+ ui_list_repos,
475
+ inputs=[user_github_token],
476
+ outputs=list_repos_output
477
+ )
478
+
479
+ # TAB 2: Issues
480
+ with gr.Tab("📋 Issues"):
481
+ gr.Markdown("### Create Issue")
482
+ issue_repo = gr.Textbox(label="Repository (username/repo)", placeholder="octocat/Hello-World")
483
+ issue_title = gr.Textbox(label="Issue Title", placeholder="Bug: Something is broken")
484
+ issue_body = gr.Textbox(label="Issue Body", placeholder="Detailed description...", lines=4)
485
+ create_issue_btn = gr.Button("Create Issue", variant="primary")
486
+ create_issue_output = gr.Textbox(label="Result", lines=6)
487
+
488
+ gr.Markdown("### List Issues")
489
+ list_issue_repo = gr.Textbox(label="Repository (username/repo)", placeholder="octocat/Hello-World")
490
+ list_issue_state = gr.Radio(["open", "closed", "all"], label="State", value="open")
491
+ list_issues_btn = gr.Button("List Issues")
492
+ list_issues_output = gr.Textbox(label="Issues", lines=10)
493
+
494
+ create_issue_btn.click(
495
+ ui_create_issue,
496
+ inputs=[issue_repo, issue_title, issue_body, user_github_token],
497
+ outputs=create_issue_output
498
+ )
499
+ list_issues_btn.click(
500
+ ui_list_issues,
501
+ inputs=[list_issue_repo, list_issue_state, user_github_token],
502
+ outputs=list_issues_output
503
+ )
504
+
505
+ # TAB 3: Custom Files
506
+ with gr.Tab("📄 Custom Files"):
507
+ gr.Markdown("### Commit File")
508
+ commit_repo = gr.Textbox(label="Repository (username/repo)", placeholder="octocat/Hello-World")
509
+ commit_path = gr.Textbox(label="File Path", placeholder="src/main.py")
510
+ commit_content = gr.Textbox(label="File Content", placeholder="print('Hello, World!')", lines=6)
511
+ commit_message = gr.Textbox(label="Commit Message", placeholder="Add main.py")
512
+ commit_file_btn = gr.Button("Commit File", variant="primary")
513
+ commit_file_output = gr.Textbox(label="Result", lines=6)
514
+
515
+ gr.Markdown("### Read File")
516
+ read_repo = gr.Textbox(label="Repository (username/repo)", placeholder="octocat/Hello-World")
517
+ read_path = gr.Textbox(label="File Path", placeholder="README.md")
518
+ read_file_btn = gr.Button("Read File")
519
+ read_file_info = gr.Textbox(label="File Info", lines=4)
520
+ read_file_content = gr.Textbox(label="File Content", lines=10)
521
+
522
+ commit_file_btn.click(
523
+ ui_commit_file,
524
+ inputs=[commit_repo, commit_path, commit_content, commit_message, user_github_token],
525
+ outputs=commit_file_output
526
+ )
527
+ read_file_btn.click(
528
+ ui_read_file,
529
+ inputs=[read_repo, read_path, user_github_token],
530
+ outputs=[read_file_info, read_file_content]
531
+ )
532
+
533
+ # TAB 4: Code Documentation
534
+ with gr.Tab("🤖 Code Documentation"):
535
+ gr.Markdown("""
536
+ ### Upload Code for AI-Powered Documentation
537
+ Upload your code file and get comprehensive documentation + audio summary!
538
+ """)
539
+
540
+ with gr.Row():
541
+ file_upload = gr.File(
542
+ label="📁 Upload Code File",
543
+ file_types=[".py", ".js", ".java", ".cpp", ".go", ".rs", ".ts", ".jsx", ".tsx", ".c", ".h"]
544
+ )
545
+ with gr.Column():
546
+ language_select = gr.Dropdown(
547
+ choices=["python", "javascript", "java", "cpp", "go", "rust", "typescript", "c"],
548
+ label="Programming Language",
549
+ value="python"
550
+ )
551
+ user_gemini_key = gr.Textbox(
552
+ label="Optional: Your Gemini API Key",
553
+ placeholder="AIzaSy... (Leave empty to use default key)",
554
+ type="password"
555
+ )
556
+
557
+ generate_btn = gr.Button("🚀 Generate Documentation + Audio Summary", variant="primary", size="lg")
558
+
559
+ gr.Markdown("### 📚 Generated Documentation")
560
+ docs_output = gr.Markdown(label="Full Documentation")
561
+
562
+ gr.Markdown("### 📝 2-Line Summary")
563
+ summary_output = gr.Textbox(label="Summary", lines=2, max_lines=2, interactive=False)
564
+
565
+ gr.Markdown("### 🔊 Audio Summary (Text-to-Speech)")
566
+ audio_output = gr.Audio(label="Listen to Summary", type="filepath")
567
+
568
+ gr.Markdown("### 💾 Commit to Repository")
569
+ gr.Markdown("Upload both the code file and generated documentation to GitHub")
570
+ with gr.Row():
571
+ commit_repo = gr.Textbox(label="Repository (username/repo)", placeholder="DemoAcc4HF/my-repo")
572
+ with gr.Row():
573
+ commit_code_path = gr.Textbox(label="Code File Path", placeholder="src/main.py")
574
+ commit_docs_path = gr.Textbox(label="Documentation Path", value="DOCUMENTATION.md")
575
+ with gr.Row():
576
+ commit_code_btn = gr.Button("Commit Code File", variant="secondary")
577
+ commit_docs_btn = gr.Button("Commit Documentation", variant="secondary")
578
+ commit_both_btn = gr.Button("Commit Both Files", variant="primary")
579
+ commit_output = gr.Textbox(label="Result", lines=6)
580
+
581
+ # Event handlers
582
+ generate_btn.click(
583
+ ui_generate_docs,
584
+ inputs=[file_upload, language_select, user_gemini_key],
585
+ outputs=[docs_output, summary_output, audio_output]
586
+ )
587
+
588
+ commit_code_btn.click(
589
+ ui_commit_code_file,
590
+ inputs=[file_upload, commit_repo, commit_code_path, user_github_token],
591
+ outputs=commit_output
592
+ )
593
+
594
+ commit_docs_btn.click(
595
+ ui_commit_docs,
596
+ inputs=[commit_repo, commit_docs_path, docs_output, user_github_token],
597
+ outputs=commit_output
598
+ )
599
+
600
+ commit_both_btn.click(
601
+ ui_commit_both,
602
+ inputs=[file_upload, commit_repo, commit_code_path, commit_docs_path, docs_output, user_github_token],
603
+ outputs=commit_output
604
+ )
605
+
606
+
607
+ gr.Markdown("""
608
+ ---
609
+ 💡 All repositories are created as **public**.
610
+ """)
611
+
612
+ return demo
613
+
614
+
615
+ if __name__ == "__main__":
616
+ # Check if GitHub token is available
617
+ if not os.environ.get("GITHUB_TOKEN"):
618
+ print("⚠️ WARNING: GITHUB_TOKEN environment variable not set!")
619
+ print(" The application will not function without a valid GitHub token.")
620
+ print(" Set it in Hugging Face Space secrets or your environment.")
621
+
622
+ # Build and launch the app
623
+ app = build_ui()
624
+
625
+ # Try to launch with MCP support if available, otherwise launch normally
626
+ try:
627
+ app.launch(
628
+ mcp_server=True,
629
+ mcp_handlers=mcp_handlers,
630
+ share=False
631
+ )
632
+ except TypeError:
633
+ # MCP not supported in this Gradio version, launch normally
634
+ print("ℹ️ Note: MCP server support not available in this Gradio version.")
635
+ print(" The UI will work, but MCP handlers won't be exposed.")
636
+ print(" For full MCP support, this will work when deployed to HF Spaces.")
637
+ app.launch(share=False)
638
+
ghclient.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GitHub Client Module
3
+ Handles all GitHub API interactions using PyGithub
4
+ """
5
+
6
+ import os
7
+ from github import Github, GithubException
8
+ from typing import Dict, List, Optional
9
+
10
+
11
+ class GitHubClient:
12
+ """GitHub API client wrapper for automation tasks"""
13
+
14
+ def __init__(self, token: Optional[str] = None):
15
+ """
16
+ Initialize GitHub client with access token
17
+
18
+ Args:
19
+ token: GitHub Personal Access Token (if None, reads from GITHUB_TOKEN env var)
20
+ """
21
+ self.token = token or os.environ.get("GITHUB_TOKEN")
22
+ if not self.token:
23
+ raise ValueError("GitHub token not provided. Set GITHUB_TOKEN environment variable.")
24
+
25
+ self.github = Github(self.token)
26
+ self.user = self.github.get_user()
27
+
28
+ def create_repo(self, name: str, private: bool = False, description: str = "") -> Dict:
29
+ """
30
+ Create a new GitHub repository
31
+
32
+ Args:
33
+ name: Repository name
34
+ private: Whether repo should be private (default: False for public)
35
+ description: Repository description
36
+
37
+ Returns:
38
+ Dict with keys: name, url, private
39
+ """
40
+ try:
41
+ repo = self.user.create_repo(
42
+ name=name,
43
+ private=private,
44
+ description=description,
45
+ auto_init=True # Initialize with README
46
+ )
47
+ return {
48
+ "name": repo.name,
49
+ "url": repo.html_url,
50
+ "private": repo.private,
51
+ "description": repo.description
52
+ }
53
+ except GithubException as e:
54
+ raise Exception(f"Failed to create repository: {e.data.get('message', str(e))}")
55
+
56
+ def list_repos(self) -> List[Dict]:
57
+ """
58
+ List all repositories for the authenticated user
59
+
60
+ Returns:
61
+ List of dicts with keys: name, url, private
62
+ """
63
+ try:
64
+ repos = self.user.get_repos()
65
+ return [
66
+ {
67
+ "name": repo.name,
68
+ "url": repo.html_url,
69
+ "private": repo.private,
70
+ "description": repo.description or ""
71
+ }
72
+ for repo in repos
73
+ ]
74
+ except GithubException as e:
75
+ raise Exception(f"Failed to list repositories: {e.data.get('message', str(e))}")
76
+
77
+ def create_issue(self, repo_full_name: str, title: str, body: str = "") -> Dict:
78
+ """
79
+ Create an issue in a repository
80
+
81
+ Args:
82
+ repo_full_name: Full repository name (e.g., "username/repo")
83
+ title: Issue title
84
+ body: Issue body/description
85
+
86
+ Returns:
87
+ Dict with keys: number, url, title, state
88
+ """
89
+ try:
90
+ self._validate_repo_name(repo_full_name)
91
+ repo = self.github.get_repo(repo_full_name)
92
+ issue = repo.create_issue(title=title, body=body)
93
+ return {
94
+ "number": issue.number,
95
+ "url": issue.html_url,
96
+ "title": issue.title,
97
+ "state": issue.state
98
+ }
99
+ except GithubException as e:
100
+ raise Exception(f"Failed to create issue: {e.data.get('message', str(e))}")
101
+
102
+ def list_issues(self, repo_full_name: str, state: str = "open") -> List[Dict]:
103
+ """
104
+ List issues in a repository
105
+
106
+ Args:
107
+ repo_full_name: Full repository name (e.g., "username/repo")
108
+ state: Issue state filter ("open", "closed", or "all")
109
+
110
+ Returns:
111
+ List of dicts with keys: number, title, url, state
112
+ """
113
+ try:
114
+ self._validate_repo_name(repo_full_name)
115
+ repo = self.github.get_repo(repo_full_name)
116
+ issues = repo.get_issues(state=state)
117
+ return [
118
+ {
119
+ "number": issue.number,
120
+ "title": issue.title,
121
+ "url": issue.html_url,
122
+ "state": issue.state
123
+ }
124
+ for issue in issues
125
+ ]
126
+ except GithubException as e:
127
+ raise Exception(f"Failed to list issues: {e.data.get('message', str(e))}")
128
+
129
+ def commit_file(self, repo_full_name: str, path: str, content: str, message: str) -> Dict:
130
+ """
131
+ Create or update a file in a repository
132
+
133
+ Args:
134
+ repo_full_name: Full repository name (e.g., "username/repo")
135
+ path: File path in repository
136
+ content: File content
137
+ message: Commit message
138
+
139
+ Returns:
140
+ Dict with keys: commit_sha, url, action (created/updated)
141
+ """
142
+ try:
143
+ self._validate_repo_name(repo_full_name)
144
+ repo = self.github.get_repo(repo_full_name)
145
+
146
+ # Check if file exists
147
+ try:
148
+ existing_file = repo.get_contents(path)
149
+ # File exists, update it
150
+ result = repo.update_file(
151
+ path=path,
152
+ message=message,
153
+ content=content,
154
+ sha=existing_file.sha
155
+ )
156
+ action = "updated"
157
+ except GithubException as e:
158
+ if e.status == 404:
159
+ # File doesn't exist, create it
160
+ result = repo.create_file(
161
+ path=path,
162
+ message=message,
163
+ content=content
164
+ )
165
+ action = "created"
166
+ else:
167
+ raise
168
+
169
+ return {
170
+ "commit_sha": result["commit"].sha,
171
+ "url": result["content"].html_url,
172
+ "action": action,
173
+ "path": path
174
+ }
175
+ except GithubException as e:
176
+ raise Exception(f"Failed to commit file: {e.data.get('message', str(e))}")
177
+
178
+ def read_file(self, repo_full_name: str, path: str) -> Dict:
179
+ """
180
+ Read a file from a repository
181
+
182
+ Args:
183
+ repo_full_name: Full repository name (e.g., "username/repo")
184
+ path: File path in repository
185
+
186
+ Returns:
187
+ Dict with keys: path, content, url
188
+ """
189
+ try:
190
+ self._validate_repo_name(repo_full_name)
191
+ repo = self.github.get_repo(repo_full_name)
192
+ file_content = repo.get_contents(path)
193
+
194
+ return {
195
+ "path": file_content.path,
196
+ "content": file_content.decoded_content.decode('utf-8'),
197
+ "url": file_content.html_url,
198
+ "size": file_content.size
199
+ }
200
+ except GithubException as e:
201
+ if e.status == 404:
202
+ raise Exception(f"File not found: {path}")
203
+ raise Exception(f"Failed to read file: {e.data.get('message', str(e))}")
204
+
205
+ @staticmethod
206
+ def _validate_repo_name(repo_full_name: str):
207
+ """
208
+ Validate repository full name format
209
+
210
+ Args:
211
+ repo_full_name: Full repository name to validate
212
+
213
+ Raises:
214
+ ValueError: If format is invalid
215
+ """
216
+ if not repo_full_name or "/" not in repo_full_name:
217
+ raise ValueError(
218
+ "Invalid repository name. Must be in format 'username/repo'"
219
+ )
220
+
221
+ parts = repo_full_name.split("/")
222
+ if len(parts) != 2 or not all(parts):
223
+ raise ValueError(
224
+ "Invalid repository name. Must be in format 'username/repo'"
225
+ )
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ PyGithub>=2.1.1
3
+ google-generativeai>=0.3.0
4
+ elevenlabs>=0.2.0
test_fibonacci.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def calculate_fibonacci(n):
2
+ """Calculate the nth Fibonacci number using recursion."""
3
+ if n <= 1:
4
+ return n
5
+ return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)
6
+
7
+ def main():
8
+ """Main function to demonstrate Fibonacci calculation."""
9
+ for i in range(10):
10
+ print(f"Fibonacci({i}) = {calculate_fibonacci(i)}")
11
+
12
+ if __name__ == "__main__":
13
+ main()