Skip to content

Commit 6d6d9b2

Browse files
wolfieschclaude
andcommitted
docs: add examples and troubleshooting
- Add examples/basic_operations.py with common GitHub operations - Add troubleshooting section to README - Add CI workflow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 499b04d commit 6d6d9b2

3 files changed

Lines changed: 296 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
test:
14+
name: Test
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: dtolnay/rust-toolchain@stable
19+
- uses: Swatinem/rust-cache@v2
20+
- run: cargo test --all-features
21+
22+
clippy:
23+
name: Clippy
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: dtolnay/rust-toolchain@stable
28+
with:
29+
components: clippy
30+
- uses: Swatinem/rust-cache@v2
31+
- run: cargo clippy -- -D warnings
32+
33+
fmt:
34+
name: Format
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v4
38+
- uses: dtolnay/rust-toolchain@stable
39+
with:
40+
components: rustfmt
41+
- run: cargo fmt --check

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,74 @@ impl FgpService for GithubService {
145145
}
146146
```
147147

148+
## Troubleshooting
149+
150+
### gh CLI Not Authenticated
151+
152+
**Symptom:** Requests fail with authentication errors
153+
154+
**Solution:**
155+
```bash
156+
# Check auth status
157+
gh auth status
158+
159+
# Re-authenticate if needed
160+
gh auth login
161+
```
162+
163+
### Permission Denied
164+
165+
**Symptom:** "Resource not accessible" or 403 errors
166+
167+
**Check:**
168+
1. Your token has required scopes: `gh auth status`
169+
2. You have access to the repository
170+
3. For private repos, ensure `repo` scope is granted
171+
172+
### Rate Limiting
173+
174+
**Symptom:** 429 errors or "rate limit exceeded"
175+
176+
**Solutions:**
177+
1. GitHub has 5000 requests/hour for authenticated users
178+
2. Check remaining: `gh api rate_limit`
179+
3. Wait for reset or reduce request frequency
180+
181+
### Slow Responses
182+
183+
**Symptom:** Calls take longer than expected
184+
185+
**Check:**
186+
1. gh CLI overhead is ~100-200ms per call
187+
2. First call may be slower (token validation)
188+
3. For bulk operations, consider batching
189+
190+
### Empty Notifications
191+
192+
**Symptom:** `notifications` returns empty when you have unread
193+
194+
**Note:** GitHub notifications can be complex:
195+
1. Check web interface for comparison
196+
2. Some notifications may be filtered by type
197+
3. Use `gh api notifications` to debug
198+
199+
### Connection Refused
200+
201+
**Symptom:** "Connection refused" when calling daemon
202+
203+
**Solution:**
204+
```bash
205+
# Check if daemon is running
206+
pgrep -f fgp-github
207+
208+
# Restart daemon
209+
fgp stop github
210+
fgp start github
211+
212+
# Verify gh is working
213+
gh api user
214+
```
215+
148216
## License
149217

150218
MIT

examples/basic_operations.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env python3
2+
"""
3+
GitHub Daemon - Basic Operations Example
4+
5+
Demonstrates common GitHub operations using the FGP GitHub daemon.
6+
Requires:
7+
- GitHub daemon running (`fgp start github`)
8+
- GitHub CLI authenticated (`gh auth login`)
9+
"""
10+
11+
import json
12+
import socket
13+
import uuid
14+
from pathlib import Path
15+
16+
SOCKET_PATH = Path.home() / ".fgp/services/github/daemon.sock"
17+
18+
19+
def call_daemon(method: str, params: dict = None) -> dict:
20+
"""Send a request to the GitHub daemon and return the response."""
21+
request = {
22+
"id": str(uuid.uuid4()),
23+
"v": 1,
24+
"method": method,
25+
"params": params or {}
26+
}
27+
28+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
29+
sock.connect(str(SOCKET_PATH))
30+
sock.sendall((json.dumps(request) + "\n").encode())
31+
32+
response = b""
33+
while True:
34+
chunk = sock.recv(4096)
35+
if not chunk:
36+
break
37+
response += chunk
38+
if b"\n" in response:
39+
break
40+
41+
return json.loads(response.decode().strip())
42+
43+
44+
def list_repos(user: str = None, limit: int = 10):
45+
"""List repositories for a user or the authenticated user."""
46+
target = user or "authenticated user"
47+
print(f"\n📚 Repositories for {target}")
48+
print("-" * 40)
49+
50+
params = {"limit": limit}
51+
if user:
52+
params["user"] = user
53+
54+
result = call_daemon("github.repos", params)
55+
56+
if result.get("ok"):
57+
repos = result["result"].get("repos", [])
58+
for repo in repos:
59+
stars = repo.get("stargazers_count", 0)
60+
print(f" • {repo.get('full_name')}")
61+
print(f" ⭐ {stars} stars | {repo.get('language', 'Unknown')}")
62+
if repo.get("description"):
63+
print(f" {repo['description'][:60]}...")
64+
print()
65+
else:
66+
print(f" ❌ Error: {result.get('error')}")
67+
68+
69+
def list_issues(repo: str, state: str = "open", limit: int = 10):
70+
"""List issues for a repository."""
71+
print(f"\n🐛 Issues for {repo} ({state})")
72+
print("-" * 40)
73+
74+
result = call_daemon("github.issues", {
75+
"repo": repo,
76+
"state": state,
77+
"limit": limit
78+
})
79+
80+
if result.get("ok"):
81+
issues = result["result"].get("issues", [])
82+
if not issues:
83+
print(f" No {state} issues found")
84+
for issue in issues:
85+
labels = ", ".join(l.get("name", "") for l in issue.get("labels", []))
86+
print(f" #{issue.get('number')} {issue.get('title')}")
87+
if labels:
88+
print(f" Labels: {labels}")
89+
print(f" Author: {issue.get('user', {}).get('login', 'unknown')}")
90+
print()
91+
else:
92+
print(f" ❌ Error: {result.get('error')}")
93+
94+
95+
def list_prs(repo: str, state: str = "open", limit: int = 10):
96+
"""List pull requests for a repository."""
97+
print(f"\n🔀 Pull Requests for {repo} ({state})")
98+
print("-" * 40)
99+
100+
result = call_daemon("github.prs", {
101+
"repo": repo,
102+
"state": state,
103+
"limit": limit
104+
})
105+
106+
if result.get("ok"):
107+
prs = result["result"].get("prs", [])
108+
if not prs:
109+
print(f" No {state} pull requests found")
110+
for pr in prs:
111+
print(f" #{pr.get('number')} {pr.get('title')}")
112+
print(f" Author: {pr.get('user', {}).get('login', 'unknown')}")
113+
print(f" Branch: {pr.get('head', {}).get('ref', 'unknown')}")
114+
print()
115+
else:
116+
print(f" ❌ Error: {result.get('error')}")
117+
118+
119+
def get_notifications(limit: int = 10):
120+
"""Get recent notifications."""
121+
print(f"\n🔔 Recent Notifications")
122+
print("-" * 40)
123+
124+
result = call_daemon("github.notifications", {"limit": limit})
125+
126+
if result.get("ok"):
127+
notifications = result["result"].get("notifications", [])
128+
if not notifications:
129+
print(" No unread notifications")
130+
for notif in notifications:
131+
print(f" • {notif.get('subject', {}).get('title', '(no title)')}")
132+
print(f" Repo: {notif.get('repository', {}).get('full_name', 'unknown')}")
133+
print(f" Type: {notif.get('subject', {}).get('type', 'unknown')}")
134+
print()
135+
else:
136+
print(f" ❌ Error: {result.get('error')}")
137+
138+
139+
def create_issue(repo: str, title: str, body: str = None, labels: list = None):
140+
"""Create a new issue."""
141+
print(f"\n➕ Creating issue in {repo}")
142+
143+
params = {
144+
"repo": repo,
145+
"title": title
146+
}
147+
if body:
148+
params["body"] = body
149+
if labels:
150+
params["labels"] = labels
151+
152+
result = call_daemon("github.create_issue", params)
153+
154+
if result.get("ok"):
155+
issue_num = result["result"].get("number")
156+
url = result["result"].get("html_url")
157+
print(f" ✅ Issue #{issue_num} created!")
158+
print(f" URL: {url}")
159+
else:
160+
print(f" ❌ Error: {result.get('error')}")
161+
162+
163+
if __name__ == "__main__":
164+
print("GitHub Daemon Examples")
165+
print("=" * 40)
166+
167+
# Check daemon health first
168+
health = call_daemon("health")
169+
if not health.get("ok"):
170+
print("❌ GitHub daemon not running. Start with: fgp start github")
171+
exit(1)
172+
173+
print("✅ GitHub daemon is healthy")
174+
175+
# Run examples - use a public repo for testing
176+
list_repos(limit=5)
177+
list_issues("fast-gateway-protocol/browser", state="open", limit=5)
178+
list_prs("fast-gateway-protocol/browser", state="open", limit=5)
179+
get_notifications(limit=5)
180+
181+
# Uncomment to create a test issue:
182+
# create_issue(
183+
# repo="your-username/test-repo",
184+
# title="Test issue from FGP",
185+
# body="This issue was created via the FGP GitHub daemon.",
186+
# labels=["test"]
187+
# )

0 commit comments

Comments
 (0)