invoking-github
Enables GitHub repository operations (read/write/commit/PR) for Claude.ai chat environments. Use when users request GitHub commits, repository updates, DEVLOG persistence, or cross-session state management via GitHub branches. Not needed in Claude Code (has native git access).
Install
npx skills add https://github.com/oaustegard/claude-skills/tree/main/plugins/github-and-git/skills/invoking-github
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oaustegard-claude-skills@llmmart
git clone https://github.com/oaustegard/claude-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole oaustegard/claude-skills collection as a plugin from our marketplace. Git is the plain clone.
README
invoking-github
Enables GitHub repository operations (read/write/commit/PR) for Claude.ai chat environments. Use when users request GitHub commits, repository updates, DEVLOG persistence, or cross-session state management via GitHub branches. Not needed in Claude Code (has native git access).
Skill manifest
Invoking GitHub
Programmatically interact with GitHub repositories from Claude.ai chat: read files, commit changes, create PRs, and persist state across sessions.
When to Use This Skill
Not needed for:
- Claude Code environments (use native git commands)
- Read-only repository access (use GitHub UI or API directly)
Quick Start
Prerequisites
Create a GitHub Personal Access Token and add to Project Knowledge:
- Go to https://github.com/settings/tokens
- Create new token (classic or fine-grained)
- Required scopes:
repo(orpublic_repofor public repos only) - In Claude.ai, add to Project Knowledge:
- Title:
GITHUB_API_KEY - Content: Your token (e.g.,
ghp_abc123...)
- Title:
Single File Commit
from invoking_github import commit_file
result = commit_file(
repo="username/repo-name",
path="README.md",
content="# Updated README\n\nNew content here...",
branch="main",
message="Update README with new instructions"
)
print(f"Committed: {result['commit_sha']}")
Read File
from invoking_github import read_file
content = read_file(
repo="username/repo-name",
path="config.json",
branch="main"
)
print(content)
Batch Commit (Multiple Files)
from invoking_github import commit_files
files = [
{"path": "src/main.py", "content": "# Python code..."},
{"path": "README.md", "content": "# Updated docs..."},
{"path": "tests/test.py", "content": "# Tests..."}
]
result = commit_files(
repo="username/repo-name",
files=files,
branch="feature-branch",
message="Add new feature implementation",
create_branch_from="main" # Create branch if it doesn't exist
)
print(f"Committed {len(files)} files: {result['commit_sha']}")
Create Pull Request
from invoking_github import create_pull_request
pr = create_pull_request(
repo="username/repo-name",
head="feature-branch",
base="main",
title="Add new feature",
body="## Changes\n- Implemented feature X\n- Updated docs\n- Added tests"
)
print(f"PR created: {pr['html_url']}")
Core Functions
read_file()
Read a file from repository:
read_file(
repo: str, # "owner/name"
path: str, # "path/to/file.py"
branch: str = "main" # Branch name
) -> str
Returns: File content as string
Raises: GitHubAPIError if file not found or access denied
commit_file()
Commit a single file (create or update):
commit_file(
repo: str, # "owner/name"
path: str, # "path/to/file.py"
content: str, # New file content
branch: str, # Target branch
message: str, # Commit message
create_branch_from: str = None # Create branch from this if doesn't exist
) -> dict
Returns: Dict with commit_sha, branch, file_path
Raises: GitHubAPIError on conflicts or auth failures
commit_files()
Commit multiple files in a single commit:
commit_files(
repo: str, # "owner/name"
files: list[dict], # [{"path": "...", "content": "..."}]
branch: str, # Target branch
message: str, # Commit message
create_branch_from: str = None # Create branch from this if doesn't exist
) -> dict
Returns: Dict with commit_sha, branch, files_committed
Raises: GitHubAPIError on failures
Note: Uses Git Trees API for efficiency - atomic commit of all files.
create_pull_request()
Create a pull request:
create_pull_request(
repo: str, # "owner/name"
head: str, # Source branch (your changes)
base: str, # Target branch (where to merge)
title: str, # PR title
body: str = "" # PR description (supports markdown)
) -> dict
Returns: Dict with number, html_url, state
Raises: GitHubAPIError if branches invalid or PR exists
Credential Configuration
This skill requires a GitHub Personal Access Token. Two configuration methods:
Method 1: Project Knowledge (Recommended)
Best for Claude.ai chat users (mobile/web):
- Create token at https://github.com/settings/tokens
- In Claude.ai Project settings → Add to Project Knowledge
- Create document titled
GITHUB_API_KEY - Paste your token as content
Permissions required:
- Classic token:
reposcope - Fine-grained token: Repository permissions → Contents (read/write) and Pull requests (read/write)
Method 2: API Credentials Skill (Fallback)
Alternatively, use combined credentials file:
- Create
API_CREDENTIALS.jsonin project knowledge - Add:
{"github_api_key": "ghp_your-token-here"}
Integration with Iterating Skill
Auto-persist DEVLOG.md to GitHub for cross-session continuity:
# In your DEVLOG update function
from invoking_github import commit_file
from pathlib import Path
def update_devlog_with_sync(data, repo="user/project", branch="devlog"):
"""Update DEVLOG.md locally and sync to GitHub"""
# Update local DEVLOG
update_devlog(data) # Your existing function
# Auto-sync to GitHub
try:
devlog_content = Path("DEVLOG.md").read_text()
result = commit_file(
repo=repo,
path="DEVLOG.md",
content=devlog_content,
branch=branch,
message=f"DEVLOG: {data['title']}",
create_branch_from="main"
)
print(f"✓ DEVLOG synced to GitHub ({repo}:{branch})")
return result
except Exception as e:
print(f"⚠ DEVLOG sync failed: {e}")
# Continue anyway - local DEVLOG.md still updated
return None
Benefits:
- Automatic backup of session progress
- Cross-device access to development logs
- Git history of decisions and progress
- No manual copy/paste to Project Knowledge
See references/iterating-integration.md for complete patterns.
Error Handling
All functions raise GitHubAPIError with descriptive messages:
from invoking_github import commit_file, GitHubAPIError
try:
result = commit_file(
repo="user/repo",
path="file.py",
content="...",
branch="main",
message="Update"
)
except GitHubAPIError as e:
if e.status_code == 404:
print("Repository or branch not found")
elif e.status_code == 401:
print("Authentication failed - check your token")
elif e.status_code == 403:
print("Access denied - check token permissions")
elif e.status_code == 409:
print("Conflict - file was modified since last read")
else:
print(f"GitHub API error: {e}")
Common errors:
- 404: Repository, branch, or file not found
- 401/403: Invalid token or insufficient permissions
- 409: Merge conflict (file changed concurrently)
- 422: Validation error (invalid branch name, etc.)
- Rate limit: Too many requests (wait before retrying)
Best Practices
Use descriptive commit messages
- Bad: "Update files"
- Good: "Add authentication middleware and update user routes"
Batch commits when possible
- Use
commit_files()for related changes - Single atomic commit = cleaner git history
- Use
Create feature branches
- Don't commit directly to main
- Use
create_branch_from="main"parameter - Create PR for review
Secure token management
- Use fine-grained tokens with minimal scopes
- Set expiration dates
- Rotate regularly
- Never commit tokens to repositories
Advanced Usage
Conditional Commits
Only commit if file content changed:
from invoking_github import read_file, commit_file, GitHubAPIError
try:
current_content = read_file(repo, path, branch)
if current_content != new_content:
commit_file(repo, path, new_content, branch, "Update file")
else:
print("No changes detected, skipping commit")
except GitHubAPIError as e:
if e.status_code == 404:
# File doesn't exist, create it
commit_file(repo, path, new_content, branch, "Create file")
else:
raise
Session-Specific Branches
Avoid conflicts with unique branch names:
import datetime
session_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
branch_name = f"devlog-{session_id}"
commit_file(
repo="user/project",
path="DEVLOG.md",
content=devlog_content,
branch=branch_name,
message="Session progress",
create_branch_from="main"
)
Multi-Repository Workflows
Work across multiple repos:
repos = ["user/frontend", "user/backend", "user/docs"]
for repo in repos:
commit_file(
repo=repo,
path="VERSION",
content="2.0.0\n",
branch="release-2.0",
message="Bump version to 2.0.0"
)
Limitations
- Target environment: Claude.ai chat only (not Claude Code)
- No OAuth: Must use Personal Access Tokens manually
- No git operations: Pure REST API (no clone, pull, rebase, etc.)
- File size: GitHub API limits ~100MB per file
- Rate limits: 5000 requests/hour for authenticated users
- Network required: All operations require internet access
See Also
- references/credential-setup.md - Step-by-step credential guide
- references/iterating-integration.md - DEVLOG auto-sync patterns
- references/troubleshooting.md - Common issues and solutions
- GitHub REST API Docs - Official GitHub API reference
Token Efficiency
This skill uses ~800 tokens when loaded but provides essential GitHub operations for claude.ai chat environments where native git access isn't available. Enables persistent state management and cross-session workflows.
Files (claude-skills)
-
references
-
credential-setup.md 6.6 KB
# Credential Setup Guide Complete guide to configuring GitHub authentication for the invoking-github skill. ## Overview This skill requires a GitHub Personal Access Token (PAT). Two configuration methods are supported: 1. **Project Knowledge** (Primary) - Recommended for claude.ai chat users 2. **API Credentials Skill** (Fallback) - For power users or local environments ## Method 1: Project Knowledge (Recommended) Best for Claude.ai chat (mobile/web/desktop). ### Step 1: Create GitHub Personal Access Token #### Option A: Fine-Grained Token (Recommended - More Secure) 1. Go to https://github.com/settings/tokens?type=beta 2. Click "Generate new token" 3. Configure: - **Token name**: `claude-ai-chat` (or your preference) - **Expiration**: 90 days (or your preference) - **Repository access**: Select specific repositories you want Claude to access - **Permissions**: - Repository permissions → **Contents**: Read and write - Repository permissions → **Pull requests**: Read and write 4. Click "Generate token" 5. **Copy the token immediately** (you won't see it again!) #### Option B: Classic Token (Easier - Less Secure) 1. Go to https://github.com/settings/tokens 2. Click "Generate new token (classic)" 3. Configure: - **Note**: `claude-ai-chat` - **Expiration**: 90 days - **Scopes**: - ✓ `repo` (Full control of private repositories) - Or just `public_repo` if you only need public repo access 4. Click "Generate token" 5. **Copy the token immediately** ### Step 2: Add Token to Project Knowledge #### In Claude.ai Web: 1. Open your project in Claude.ai 2. Click the project name/settings 3. Click "Add to Project Knowledge" 4. Create a new document: - **Title**: `GITHUB_API_KEY` (exactly this, all caps) - **Content**: Paste your token (e.g., `ghp_1234567890abcdefgh...`) 5. Click "Add" or "Save" #### In Claude.ai Mobile (iOS/Android): 1. Open your project 2. Tap the menu icon 3. Select "Project Knowledge" 4. Tap "Add document" 5. Title: `GITHUB_API_KEY` 6. Content: Paste your token 7. Save ### Step 3: Verify Configuration Ask Claude to run this test: ```python import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import get_github_token try: token = get_github_token() masked = f"{token[:7]}...{token[-4:]}" print(f"✓ Token found: {masked}") print(f"✓ Token length: {len(token)} characters") except ValueError as e: print(f"✗ Token not found") print(e) ``` **Expected output**: ``` ✓ Token found: ghp_123...xyz ✓ Token length: 40 characters ``` ## Method 2: API Credentials Skill (Fallback) For power users who prefer file-based configuration. ### Step 1: Create Token Follow the same token creation steps as Method 1. ### Step 2: Add to api-credentials config.json 1. Edit `/home/user/claude-skills/api-credentials/config.json` 2. Add your token: ```json { "anthropic_api_key": "sk-ant-...", "google_api_key": "AIza...", "github_api_key": "ghp_your_token_here" } ``` 3. Save the file ### Step 3: Verify Configuration Same test as Method 1 above. ## Security Best Practices ### 1. Use Fine-Grained Tokens Fine-grained tokens are more secure because: - Limited to specific repositories - Specific permissions (not full repo access) - Easier to audit ### 2. Set Expiration Dates - Recommended: 90 days - Maximum: 1 year (but not recommended) - Never select "No expiration" for production use ### 3. Minimal Permissions Only grant permissions you actually need: | Operation | Required Permissions | |-----------|---------------------| | Read files | Contents: Read | | Commit files | Contents: Read and write | | Create PRs | Contents: Read and write + Pull requests: Read and write | ### 4. Token Rotation Rotate tokens regularly: - Set calendar reminder before expiration - Create new token - Update Project Knowledge or config.json - Revoke old token ### 5. Never Commit Tokens - **Never** paste tokens in code that gets committed - **Never** commit config.json with real tokens - Use .gitignore (api-credentials/config.json is already gitignored) ### 6. Revoke Compromised Tokens If a token is accidentally exposed: 1. Go to https://github.com/settings/tokens 2. Find the token 3. Click "Delete" or "Revoke" 4. Create a new token immediately 5. Update configuration ## Troubleshooting ### "No GitHub API token found!" **Problem**: Token not detected in either location. **Solutions**: - Check Project Knowledge document is titled **exactly** `GITHUB_API_KEY` (all caps) - Verify token is the only content in the document (no extra text) - Check for trailing whitespace - Try Method 2 (api-credentials) as a test ### "Authentication failed" **Problem**: Token is invalid or expired. **Solutions**: - Verify token hasn't expired (check GitHub settings) - Ensure no extra characters when copying token - Generate a new token and update configuration ### "Access denied" **Problem**: Token lacks required permissions. **Solutions**: - For fine-grained tokens: Check repository access includes your target repo - For fine-grained tokens: Verify Contents and Pull requests permissions - For classic tokens: Ensure `repo` scope is selected - Generate new token with correct permissions ### Token works in web but not mobile **Problem**: Project Knowledge might not sync properly. **Solutions**: - Wait a few minutes for sync - Refresh the app - Try deleting and re-adding the document - Use api-credentials method as workaround (if app supports file access) ## FAQ **Q: Can I use the GitHub OAuth connection in claude.ai UI?** A: No, the OAuth connection in the UI is not accessible to skills. You must create a Personal Access Token manually. **Q: How long do tokens last?** A: Based on your selection during creation. Recommended: 90 days with rotation. **Q: Can I use the same token across multiple Claude projects?** A: Yes, but for security, consider creating separate tokens per project with limited repo access. **Q: What happens when my token expires?** A: The skill will fail with authentication errors. Create a new token and update your configuration. **Q: Can I use a GitHub App token?** A: Not directly. This skill is designed for Personal Access Tokens. GitHub App tokens have different authentication flows. **Q: Is my token secure in Project Knowledge?** A: Project Knowledge is stored securely, but treat it like a password - use minimal permissions, set expiration, rotate regularly. ## Next Steps - [SKILL.md](../SKILL.md) - Core skill documentation - [iterating-integration.md](iterating-integration.md) - Auto-sync DEVLOG patterns - [troubleshooting.md](troubleshooting.md) - Common issues and solutions -
iterating-integration.md 12.6 KB
# Iterating Skill Integration Patterns for integrating invoking-github with the iterating skill to enable automatic DEVLOG persistence to GitHub. ## Overview The iterating skill tracks development progress across sessions using DEVLOG.md. By integrating with invoking-github, we can automatically persist this log to a GitHub branch, enabling: - **Cross-session continuity**: Resume work across different Claude environments - **Version control**: Git history of all decisions and progress - **Team collaboration**: Share development logs with team members - **Backup**: Automatic backup of session progress ## Basic Integration Pattern ### Modified Update DEVLOG Function ```python from pathlib import Path from datetime import datetime def update_devlog_with_github_sync( data: dict, repo: str, branch: str = "devlog", auto_sync: bool = True ): """ Update DEVLOG.md locally and optionally sync to GitHub. Args: data: DEVLOG entry data (title, prev, now, work, etc.) repo: GitHub repository ("owner/name") branch: Target branch for DEVLOG auto_sync: Whether to automatically sync to GitHub """ from iterating import update_devlog # Your existing function # Update local DEVLOG update_devlog(data) # Optionally sync to GitHub if auto_sync: try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_file devlog_content = Path("DEVLOG.md").read_text() result = commit_file( repo=repo, path="DEVLOG.md", content=devlog_content, branch=branch, message=f"DEVLOG: {data['title']}", create_branch_from="main" ) print(f"✓ DEVLOG synced to GitHub ({repo}:{branch}) - {result['commit_sha'][:7]}") return result except Exception as e: print(f"⚠ DEVLOG sync failed: {e}") print(" Local DEVLOG.md updated successfully") return None ``` ### Usage ```python # At end of session data = { 'title': 'Implemented authentication middleware', 'prev': 'Designed auth flow', 'now': 'Built JWT middleware', 'work': { 'added': ['src/auth/middleware.py'], 'changed': ['src/app.py'], 'fixed': ['Auth validation bug'] }, 'decisions': [ { 'what': 'Use JWT tokens', 'why': 'Stateless, scalable', 'alt': 'Session cookies' } ], 'next': ['Add token refresh', 'Test edge cases'] } update_devlog_with_github_sync( data=data, repo="username/project-name", branch="devlog" ) ``` ## Configuration-Based Pattern For users who want to configure once and forget: ### Configuration via Project Knowledge Create `GITHUB_DEVLOG_CONFIG` in Project Knowledge: ```json { "repo": "username/project-name", "branch": "devlog", "auto_sync": true, "create_branch_from": "main" } ``` ### Configuration-Aware Function ```python import json from pathlib import Path def get_devlog_config(): """Load DEVLOG GitHub sync configuration""" config_path = Path("/mnt/project/GITHUB_DEVLOG_CONFIG") if config_path.exists(): try: config = json.loads(config_path.read_text()) return config except (json.JSONDecodeError, IOError): pass # Default config (no sync) return {"auto_sync": False} def update_devlog_smart(data: dict): """ Update DEVLOG with automatic GitHub sync based on configuration. """ from iterating import update_devlog # Update local DEVLOG update_devlog(data) # Check if GitHub sync is configured config = get_devlog_config() if config.get("auto_sync"): try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_file devlog_content = Path("DEVLOG.md").read_text() result = commit_file( repo=config["repo"], path="DEVLOG.md", content=devlog_content, branch=config.get("branch", "devlog"), message=f"DEVLOG: {data['title']}", create_branch_from=config.get("create_branch_from", "main") ) print(f"✓ DEVLOG synced to {config['repo']}:{config['branch']}") except Exception as e: print(f"⚠ DEVLOG sync failed: {e}") ``` ## Session-Specific Branch Pattern Avoid conflicts by using unique branch names per session: ```python from datetime import datetime def update_devlog_with_session_branch(data: dict, repo: str): """ Update DEVLOG and sync to session-specific branch. """ from iterating import update_devlog # Update local DEVLOG update_devlog(data) # Generate session-specific branch name session_id = datetime.now().strftime("%Y%m%d-%H%M%S") branch_name = f"devlog-{session_id}" try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_file devlog_content = Path("DEVLOG.md").read_text() result = commit_file( repo=repo, path="DEVLOG.md", content=devlog_content, branch=branch_name, message=f"DEVLOG: {data['title']}", create_branch_from="main" ) print(f"✓ DEVLOG synced to {repo}:{branch_name}") print(f" Commit: {result['commit_sha'][:7]}") print(f" To merge: Create PR from {branch_name} to main") except Exception as e: print(f"⚠ DEVLOG sync failed: {e}") ``` ## Multi-File Session State Pattern Persist entire session state (not just DEVLOG): ```python def persist_session_state( repo: str, branch: str, files: dict[str, str], message: str ): """ Persist multiple files representing session state. Args: repo: GitHub repository branch: Target branch files: Dict of {file_path: content} message: Commit message """ try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_files # Convert dict to list format files_list = [ {"path": path, "content": content} for path, content in files.items() ] result = commit_files( repo=repo, files=files_list, branch=branch, message=message, create_branch_from="main" ) print(f"✓ Session state synced: {result['files_committed']} files") return result except Exception as e: print(f"⚠ State sync failed: {e}") return None # Usage session_files = { "DEVLOG.md": Path("DEVLOG.md").read_text(), "session_notes.md": "# Session Notes\n...", "current_progress.json": json.dumps(progress_data) } persist_session_state( repo="user/project", branch="session-state", files=session_files, message="Session checkpoint" ) ``` ## Progressive Summarization Pattern Maintain both detailed and summarized DEVLOGs: ```python def update_devlog_with_summary( data: dict, repo: str, branch: str = "devlog" ): """ Update DEVLOG and maintain a summary file. """ from iterating import update_devlog # Update local DEVLOG update_devlog(data) # Generate or update summary summary = generate_devlog_summary() # Your summarization logic try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_files devlog_content = Path("DEVLOG.md").read_text() files = [ {"path": "DEVLOG.md", "content": devlog_content}, {"path": "DEVLOG_SUMMARY.md", "content": summary} ] result = commit_files( repo=repo, files=files, branch=branch, message=f"DEVLOG: {data['title']} (with summary)", create_branch_from="main" ) print(f"✓ DEVLOG and summary synced") except Exception as e: print(f"⚠ Sync failed: {e}") def generate_devlog_summary(): """ Generate a summary of DEVLOG.md. Extract key decisions, milestones, open items. """ devlog = Path("DEVLOG.md").read_text() # Simple summarization (you can enhance this) summary_parts = [ "# DEVLOG Summary", "", "## Key Decisions", "<!-- Extract from DEVLOG -->", "", "## Open Items", "<!-- Extract from DEVLOG -->", "", "## Next Steps", "<!-- Extract from DEVLOG -->" ] return "\n".join(summary_parts) ``` ## Cross-Environment Continuity Resume work from any Claude environment by reading DEVLOG from GitHub: ```python def resume_session_from_github(repo: str, branch: str = "devlog"): """ Resume a session by reading DEVLOG from GitHub. """ try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import read_file devlog_content = read_file( repo=repo, path="DEVLOG.md", branch=branch ) # Save to local working directory Path("DEVLOG.md").write_text(devlog_content) print(f"✓ Resumed session from {repo}:{branch}") print("\nRecent entries:") # Show last 20 lines recent = "\n".join(devlog_content.split("\n")[-20:]) print(recent) return devlog_content except Exception as e: print(f"✗ Could not resume session: {e}") return None # Usage at start of new session print("Resuming previous session...") resume_session_from_github("user/project", "devlog") ``` ## Automatic PR Creation Pattern At session end, automatically create PR with DEVLOG: ```python def finalize_session_with_pr( data: dict, repo: str, session_branch: str, message: str = "Session progress" ): """ Finalize session by syncing DEVLOG and creating PR. """ from iterating import update_devlog # Update local DEVLOG update_devlog(data) try: import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import commit_file, create_pull_request # Commit DEVLOG devlog_content = Path("DEVLOG.md").read_text() commit_result = commit_file( repo=repo, path="DEVLOG.md", content=devlog_content, branch=session_branch, message=message, create_branch_from="main" ) print(f"✓ DEVLOG committed: {commit_result['commit_sha'][:7]}") # Create PR pr = create_pull_request( repo=repo, head=session_branch, base="main", title=f"Session: {data['title']}", body=f"## Session Summary\n\n{message}\n\n## DEVLOG\n\nSee DEVLOG.md for details." ) print(f"✓ PR created: {pr['html_url']}") return pr except Exception as e: print(f"⚠ Finalization failed: {e}") return None ``` ## Best Practices ### 1. Graceful Degradation Always update local DEVLOG first, sync as bonus: ```python # Good update_devlog(data) # Always succeeds try: sync_to_github() # Bonus feature except: pass # Bad try: sync_to_github() # If this fails, local DEVLOG not updated update_devlog(data) except: pass ``` ### 2. Informative Messages Tell user what's happening: ```python print("Updating DEVLOG...") update_devlog(data) print("✓ DEVLOG updated locally") print("Syncing to GitHub...") sync_to_github() print("✓ Synced to GitHub") ``` ### 3. Configuration Over Code Use Project Knowledge for configuration: ```json { "repo": "user/project", "branch": "devlog", "auto_sync": true } ``` ### 4. Session Identifiers Use timestamps or UUIDs for unique branches: ```python # Good f"devlog-{datetime.now().isoformat()}" f"devlog-{uuid.uuid4().hex[:8]}" # Avoid "devlog" # Can conflict across sessions ``` ## Troubleshooting **DEVLOG syncs but shows conflicts** → Use session-specific branches **Sync is slow** → Normal, GitHub API is network-dependent **Sync fails silently** → Check error handling, print exceptions **DEVLOG not resuming correctly** → Verify branch name, check GitHub token permissions ## Next Steps - [SKILL.md](../SKILL.md) - Core skill documentation - [credential-setup.md](credential-setup.md) - Token configuration - [troubleshooting.md](troubleshooting.md) - Common issues -
troubleshooting.md 10.3 KB
# Troubleshooting Guide Common issues and solutions when using the invoking-github skill. ## Credential Issues ### "No GitHub API token found!" **Symptoms**: - Any operation fails immediately with credential error - Self-test shows "Token not configured" **Causes**: 1. Token not added to Project Knowledge 2. Wrong document title in Project Knowledge 3. api-credentials config.json missing or malformed **Solutions**: **Check Project Knowledge**: - Document must be titled exactly `GITHUB_API_KEY` (all caps, underscores) - Content should be just the token, no extra text - No trailing whitespace or line breaks **Check api-credentials**: ```bash cat /home/user/claude-skills/api-credentials/config.json ``` Should contain: ```json { "github_api_key": "ghp_your_token_here" } ``` **Test credential detection**: ```python import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import get_github_token try: token = get_github_token() print(f"✓ Token found: {token[:7]}...{token[-4:]}") except ValueError as e: print(e) ``` ### "Authentication failed" (401) **Symptoms**: - Token is detected but API calls fail with 401 - "Authentication failed" error message **Causes**: 1. Token has expired 2. Token was revoked 3. Token contains typos or extra characters **Solutions**: **Check token status on GitHub**: 1. Go to https://github.com/settings/tokens 2. Find your token 3. Check if it's expired or revoked **Generate new token**: 1. Create new token at https://github.com/settings/tokens 2. Update Project Knowledge or config.json 3. Retry operation **Verify no extra characters**: - Token should start with `ghp_` (classic) or `github_pat_` (fine-grained) - No spaces, quotes, or line breaks - Exact length: 40 characters (classic) or longer (fine-grained) ### "Access denied" (403) **Symptoms**: - Authentication works but specific operations fail - "Access denied" or "insufficient permissions" errors **Causes**: 1. Token lacks required permissions 2. Repository is private and token doesn't have access 3. Rate limit exceeded **Solutions**: **Check token permissions**: - For fine-grained tokens: - Repository access: Verify target repo is in the list - Permissions: Contents (read/write), Pull requests (read/write) - For classic tokens: - Scope: Must have `repo` (or `public_repo` for public repos only) **Check rate limits**: ```python import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import _make_api_request response = _make_api_request("/rate_limit") print(f"Remaining: {response['rate']['remaining']}/{response['rate']['limit']}") print(f"Reset at: {response['rate']['reset']}") ``` If rate limited: - Wait until reset time - Reduce request frequency - Consider using different token ## Repository Issues ### "Resource not found" (404) **Symptoms**: - Repository, branch, or file not found - "404" error message **Causes**: 1. Repository name is incorrect 2. Branch doesn't exist 3. File path is wrong 4. Repository is private and token doesn't have access **Solutions**: **Verify repository format**: - Correct: `username/repo-name` or `organization/repo-name` - Incorrect: `https://github.com/username/repo-name` - Incorrect: `username-repo-name` - Incorrect: `repo-name` (missing owner) **Check branch exists**: ```python import sys sys.path.append('/home/user/claude-skills/invoking-github/scripts') from github_client import _make_api_request # List all branches response = _make_api_request("/repos/username/repo-name/branches") branches = [b['name'] for b in response] print(f"Available branches: {branches}") ``` **Verify file path**: - Use forward slashes: `src/main.py` not `src\main.py` - No leading slash: `README.md` not `/README.md` - Case-sensitive: `readme.md` ≠ `README.md` ### "Conflict" (409) **Symptoms**: - Commit fails with 409 error - "Conflict" or "file was modified" message **Causes**: 1. File was modified since last read 2. Multiple concurrent commits to same file 3. Branch was force-pushed **Solutions**: **Read file again before committing**: ```python from github_client import read_file, commit_file # Read current version current = read_file(repo, path, branch) # Make your changes new_content = modify(current) # Commit (will have latest SHA) commit_file(repo, path, new_content, branch, "Update") ``` **Use unique branches per session**: ```python from datetime import datetime branch = f"feature-{datetime.now().strftime('%Y%m%d-%H%M%S')}" commit_file(repo, path, content, branch, "Update", create_branch_from="main") ``` **Check for concurrent modifications**: - Ensure only one Claude session is modifying the file - Use session-specific branches - Coordinate with team members ## File Operation Issues ### File content is corrupted **Symptoms**: - File appears as binary/garbage after commit - Encoding errors when reading **Causes**: 1. Binary file being treated as text 2. Encoding issues **Solutions**: **This skill is for text files only**: - Supports: .txt, .md, .py, .js, .json, .yaml, etc. - Not supported: Images, PDFs, executables, archives **Ensure UTF-8 encoding**: ```python # Read with explicit encoding content = Path("file.txt").read_text(encoding='utf-8') # Commit commit_file(repo, path, content, branch, "Update") ``` ### Large file fails to commit **Symptoms**: - Commit fails for large files - Timeout errors **Causes**: 1. File exceeds GitHub API limits (~100MB) 2. Network timeout **Solutions**: **Check file size**: ```python size = len(content.encode('utf-8')) if size > 100 * 1024 * 1024: # 100MB print(f"File too large: {size / (1024*1024):.1f}MB") ``` **Split large files**: - Break into smaller chunks - Use Git LFS (not supported by this skill) - Consider alternative storage ## Network Issues ### "Network error" or timeouts **Symptoms**: - Operations fail with network errors - Timeouts during API calls **Causes**: 1. No internet connection 2. GitHub API is down 3. Firewall blocking requests **Solutions**: **Check internet connection**: ```bash curl -I https://api.github.com ``` **Check GitHub status**: - Visit https://www.githubstatus.com - Check for ongoing incidents **Retry with backoff**: ```python import time for attempt in range(3): try: result = commit_file(...) break except Exception as e: if attempt < 2: wait = 2 ** attempt print(f"Retry in {wait}s...") time.sleep(wait) else: raise ``` ## Integration Issues ### DEVLOG sync silently fails **Symptoms**: - Local DEVLOG updates but no GitHub commit - No error messages **Causes**: 1. Exception is being caught and suppressed 2. auto_sync is False **Solutions**: **Add logging**: ```python try: sync_to_github() except Exception as e: print(f"⚠ Sync failed: {e}") import traceback traceback.print_exc() ``` **Check configuration**: ```python config = get_devlog_config() print(f"auto_sync: {config.get('auto_sync')}") print(f"repo: {config.get('repo')}") ``` ### Can't import github_client **Symptoms**: - `ImportError: No module named github_client` - `ModuleNotFoundError` **Causes**: 1. Path not in sys.path 2. Skill not installed **Solutions**: **Add to path explicitly**: ```python import sys sys.path.insert(0, '/home/user/claude-skills/invoking-github/scripts') from github_client import commit_file ``` **Verify skill exists**: ```bash ls -la /home/user/claude-skills/invoking-github/scripts/ ``` Should show: - `github_client.py` - `__init__.py` ## API Behavior Issues ### Commit succeeds but file doesn't update **Symptoms**: - Commit returns success - File on GitHub hasn't changed **Causes**: 1. Committing to wrong branch 2. Looking at different branch on GitHub **Solutions**: **Verify branch**: ```python result = commit_file(repo, path, content, branch, "Update") print(f"Committed to branch: {result['branch']}") print(f"Commit SHA: {result['commit_sha']}") ``` **Check GitHub UI**: - Make sure you're viewing the correct branch - Click branch dropdown and select your target branch ### PR creation fails: "A pull request already exists" **Symptoms**: - PR creation fails with "already exists" error **Causes**: 1. PR from head to base already exists 2. Previous PR wasn't closed **Solutions**: **List existing PRs**: ```python from github_client import _make_api_request response = _make_api_request(f"/repos/{repo}/pulls?state=open") for pr in response: print(f"PR #{pr['number']}: {pr['head']['ref']} → {pr['base']['ref']}") ``` **Close or merge existing PR first**: - Go to GitHub and close/merge the existing PR - Or use a different head branch ## Performance Issues ### Operations are slow **Symptoms**: - Each commit takes several seconds - Batch operations take a long time **Causes**: 1. Network latency 2. Large files 3. Many files in batch **Solutions**: **Use batch operations**: ```python # Slow: Multiple individual commits for file in files: commit_file(repo, file['path'], file['content'], branch, f"Update {file['path']}") # Fast: Single batch commit commit_files(repo, files, branch, "Update multiple files") ``` **Minimize requests**: - Don't read file before every commit if you already have content - Use commit_files() instead of multiple commit_file() calls ## Getting Help If you're still stuck: 1. **Check GitHub API status**: https://www.githubstatus.com 2. **Verify token permissions**: https://github.com/settings/tokens 3. **Test with GitHub CLI**: Try same operation with `gh` CLI 4. **Check GitHub docs**: https://docs.github.com/rest **Provide this info when asking for help**: - Error message (full text) - Operation you're trying to perform - Repository name (if not sensitive) - Token type (classic vs fine-grained) - Whether credential test passes ## Common Error Reference | Error Code | Meaning | Common Solution | |------------|---------|-----------------| | 401 | Authentication failed | Check token is valid and not expired | | 403 | Access denied | Check token permissions and rate limits | | 404 | Not found | Verify repo/branch/file exists and is accessible | | 409 | Conflict | Re-read file, use unique branch names | | 422 | Validation error | Check branch name format, commit message | ## Next Steps - [credential-setup.md](credential-setup.md) - Token configuration guide - [SKILL.md](../SKILL.md) - Main skill documentation - [GitHub API Docs](https://docs.github.com/rest) - Official API reference
-
-
scripts
-
github_client.py 16.1 KB
#!/usr/bin/env python3 """ GitHub API Client for Claude.ai Chat Environments Provides programmatic GitHub operations (read/write/commit/PR) using REST API. Designed for claude.ai chat where native git access isn't available. """ import base64 import json import os from pathlib import Path from urllib import error, parse, request class GitHubAPIError(Exception): """ Custom exception for GitHub API errors. Attributes: message: Error description status_code: HTTP status code (if applicable) response: Full response data (if available) """ def __init__(self, message: str, status_code: int | None = None, response: dict | None = None): self.message = message self.status_code = status_code self.response = response super().__init__(self.message) def get_github_token() -> str: """ Get GitHub API token from environment or project knowledge files. Priority order: 1. GH_TOKEN or GITHUB_TOKEN environment variable 2. GitHub.env project file (GH_TOKEN=...) 3. Individual file: /mnt/project/GITHUB_API_KEY.txt 4. Combined file: /mnt/project/API_CREDENTIALS.json Returns: str: GitHub Personal Access Token Raises: ValueError: If no token found in any source """ # Pattern 0: Environment variables (standard conventions) for var in ("GH_TOKEN", "GITHUB_TOKEN", "GITHUB_API_KEY"): token = os.environ.get(var, "").strip() if token: return token # Pattern 0b: GitHub.env project file gh_env = Path("/mnt/project/GitHub.env") if gh_env.exists(): try: for line in gh_env.read_text().splitlines(): line = line.strip() if line.startswith("GH_TOKEN="): token = line[len("GH_TOKEN="):].strip().strip('"').strip("'") if token: return token except OSError: pass # Fall through to other methods # Pattern 1: Individual key file (recommended) key_file = Path("/mnt/project/GITHUB_API_KEY.txt") if key_file.exists(): try: token = key_file.read_text().strip() if token: return token except OSError as e: raise ValueError( f"Found GITHUB_API_KEY.txt but couldn't read it: {e}\n" f"Please check file permissions or recreate the file" ) # Pattern 2: Combined credentials file creds_file = Path("/mnt/project/API_CREDENTIALS.json") if creds_file.exists(): try: with open(creds_file) as f: config = json.load(f) token = config.get("github_api_key", "").strip() if token: return token except (json.JSONDecodeError, OSError) as e: raise ValueError( f"Found API_CREDENTIALS.json but couldn't parse it: {e}\n" f"Please check file format" ) # No token found - provide helpful error message raise ValueError( "No GitHub API token found!\n\n" "Provide a token using one of these methods:\n\n" "Option 1: Environment variable\n" " export GH_TOKEN=ghp_...\n\n" "Option 2: GitHub.env project file\n" " GH_TOKEN=ghp_...\n\n" "Option 3: Individual file\n" " File: GITHUB_API_KEY.txt\n" " Content: ghp_...\n\n" "Option 4: Combined file\n" " File: API_CREDENTIALS.json\n" " Content: {\"github_api_key\": \"ghp_...\"}\n\n" "Generate token at: https://github.com/settings/tokens\n" "Required scopes:\n" " - Classic token: 'repo' scope\n" " - Fine-grained token: Repository permissions → Contents (read/write) + Pull requests (read/write)" ) def _make_api_request( endpoint: str, method: str = "GET", data: dict | None = None, token: str | None = None ) -> dict: """ Make authenticated GitHub API request. Args: endpoint: API endpoint (e.g., "/repos/owner/name/contents/file") method: HTTP method (GET, POST, PUT, PATCH, DELETE) data: Request body (will be JSON-encoded) token: GitHub API token (if None, will fetch automatically) Returns: Dict: Parsed JSON response Raises: GitHubAPIError: On API errors """ if token is None: token = get_github_token() url = f"https://api.github.com{endpoint}" headers = { "Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", "User-Agent": "Claude-Invoking-GitHub-Skill/1.0" } # Prepare request req_data = None if data is not None: req_data = json.dumps(data).encode('utf-8') headers["Content-Type"] = "application/json" req = request.Request(url, data=req_data, headers=headers, method=method) try: with request.urlopen(req) as response: response_data = response.read().decode('utf-8') if response_data: return json.loads(response_data) return {} except error.HTTPError as e: error_body = e.read().decode('utf-8') try: error_json = json.loads(error_body) error_message = error_json.get('message', str(e)) except json.JSONDecodeError: error_message = error_body or str(e) # Provide helpful error messages based on status code if e.code == 401: raise GitHubAPIError( "Authentication failed. Please check your GitHub token is valid and not expired.", status_code=401, response=error_json if 'error_json' in locals() else None ) elif e.code == 403: if 'rate limit' in error_message.lower(): raise GitHubAPIError( "GitHub API rate limit exceeded. Please wait before making more requests.", status_code=403, response=error_json if 'error_json' in locals() else None ) else: raise GitHubAPIError( f"Access denied. Check that your token has required permissions: {error_message}", status_code=403, response=error_json if 'error_json' in locals() else None ) elif e.code == 404: raise GitHubAPIError( f"Resource not found: {error_message}", status_code=404, response=error_json if 'error_json' in locals() else None ) elif e.code == 409: raise GitHubAPIError( f"Conflict: {error_message}. The resource may have been modified concurrently.", status_code=409, response=error_json if 'error_json' in locals() else None ) elif e.code == 422: raise GitHubAPIError( f"Validation failed: {error_message}", status_code=422, response=error_json if 'error_json' in locals() else None ) else: raise GitHubAPIError( f"GitHub API error ({e.code}): {error_message}", status_code=e.code, response=error_json if 'error_json' in locals() else None ) except error.URLError as e: raise GitHubAPIError(f"Network error: {e.reason}") def read_file(repo: str, path: str, branch: str = "main") -> str: """ Read a file from GitHub repository. Args: repo: Repository in format "owner/name" path: File path within repository branch: Branch name (default: main) Returns: str: File content Raises: GitHubAPIError: If file not found or access denied Example: >>> content = read_file("octocat/Hello-World", "README", "main") >>> print(content) """ endpoint = f"/repos/{repo}/contents/{path}" params = {"ref": branch} endpoint_with_params = f"{endpoint}?{parse.urlencode(params)}" response = _make_api_request(endpoint_with_params, method="GET") if response.get("type") != "file": raise GitHubAPIError(f"'{path}' is not a file (type: {response.get('type')})") # GitHub returns content as base64-encoded content_b64 = response.get("content", "") content = base64.b64decode(content_b64).decode('utf-8') return content def commit_file( repo: str, path: str, content: str, branch: str, message: str, create_branch_from: str | None = None ) -> dict: """ Commit a single file (create or update). Args: repo: Repository in format "owner/name" path: File path within repository content: New file content branch: Target branch message: Commit message create_branch_from: If branch doesn't exist, create from this branch Returns: Dict with commit_sha, branch, file_path Raises: GitHubAPIError: On conflicts or auth failures Example: >>> result = commit_file( ... repo="user/repo", ... path="README.md", ... content="# Hello World", ... branch="main", ... message="Update README" ... ) >>> print(f"Committed: {result['commit_sha']}") """ # Check if branch exists, create if needed if create_branch_from: _ensure_branch_exists(repo, branch, create_branch_from) # Get current file SHA if it exists (needed for updates) file_sha = None try: current_file = _make_api_request( f"/repos/{repo}/contents/{path}?ref={branch}", method="GET" ) file_sha = current_file.get("sha") except GitHubAPIError as e: if e.status_code != 404: raise # Re-raise if not a "file not found" error # File doesn't exist, will be created # Encode content as base64 content_b64 = base64.b64encode(content.encode('utf-8')).decode('utf-8') # Commit file data = { "message": message, "content": content_b64, "branch": branch } if file_sha: data["sha"] = file_sha # Required for updates response = _make_api_request( f"/repos/{repo}/contents/{path}", method="PUT", data=data ) return { "commit_sha": response["commit"]["sha"], "branch": branch, "file_path": path } def commit_files( repo: str, files: list[dict[str, str]], branch: str, message: str, create_branch_from: str | None = None ) -> dict: """ Commit multiple files in a single commit using Git Trees API. Args: repo: Repository in format "owner/name" files: List of dicts with 'path' and 'content' keys branch: Target branch message: Commit message create_branch_from: If branch doesn't exist, create from this branch Returns: Dict with commit_sha, branch, files_committed (count) Raises: GitHubAPIError: On failures Example: >>> files = [ ... {"path": "file1.txt", "content": "Content 1"}, ... {"path": "dir/file2.txt", "content": "Content 2"} ... ] >>> result = commit_files( ... repo="user/repo", ... files=files, ... branch="main", ... message="Add multiple files" ... ) >>> print(f"Committed {result['files_committed']} files") """ # Check if branch exists, create if needed if create_branch_from: _ensure_branch_exists(repo, branch, create_branch_from) # Get current branch reference ref_data = _make_api_request(f"/repos/{repo}/git/refs/heads/{branch}") base_commit_sha = ref_data["object"]["sha"] # Get base tree SHA base_commit = _make_api_request(f"/repos/{repo}/git/commits/{base_commit_sha}") base_tree_sha = base_commit["tree"]["sha"] # Create blobs for each file tree_items = [] for file_info in files: # Create blob blob_data = { "content": file_info["content"], "encoding": "utf-8" } blob_response = _make_api_request( f"/repos/{repo}/git/blobs", method="POST", data=blob_data ) tree_items.append({ "path": file_info["path"], "mode": "100644", # Regular file "type": "blob", "sha": blob_response["sha"] }) # Create tree tree_data = { "base_tree": base_tree_sha, "tree": tree_items } tree_response = _make_api_request( f"/repos/{repo}/git/trees", method="POST", data=tree_data ) # Create commit commit_data = { "message": message, "tree": tree_response["sha"], "parents": [base_commit_sha] } commit_response = _make_api_request( f"/repos/{repo}/git/commits", method="POST", data=commit_data ) # Update branch reference _make_api_request( f"/repos/{repo}/git/refs/heads/{branch}", method="PATCH", data={"sha": commit_response["sha"]} ) return { "commit_sha": commit_response["sha"], "branch": branch, "files_committed": len(files) } def create_pull_request( repo: str, head: str, base: str, title: str, body: str = "" ) -> dict: """ Create a pull request. Args: repo: Repository in format "owner/name" head: Source branch (where your changes are) base: Target branch (where you want to merge) title: PR title body: PR description (supports markdown) Returns: Dict with number, html_url, state Raises: GitHubAPIError: If branches invalid or PR already exists Example: >>> pr = create_pull_request( ... repo="user/repo", ... head="feature-branch", ... base="main", ... title="Add new feature", ... body="## Changes\\n- Added feature X" ... ) >>> print(f"PR created: {pr['html_url']}") """ data = { "title": title, "head": head, "base": base, "body": body } response = _make_api_request( f"/repos/{repo}/pulls", method="POST", data=data ) return { "number": response["number"], "html_url": response["html_url"], "state": response["state"] } def _ensure_branch_exists(repo: str, branch: str, create_from: str) -> None: """ Ensure a branch exists, creating it if necessary. Args: repo: Repository in format "owner/name" branch: Branch name to ensure exists create_from: Branch to create from if it doesn't exist Raises: GitHubAPIError: On failures """ try: # Check if branch exists _make_api_request(f"/repos/{repo}/git/refs/heads/{branch}") # Branch exists, nothing to do except GitHubAPIError as e: if e.status_code == 404: # Branch doesn't exist, create it # Get SHA of source branch source_ref = _make_api_request(f"/repos/{repo}/git/refs/heads/{create_from}") source_sha = source_ref["object"]["sha"] # Create new branch _make_api_request( f"/repos/{repo}/git/refs", method="POST", data={ "ref": f"refs/heads/{branch}", "sha": source_sha } ) else: raise # Re-raise other errors # Export main functions __all__ = [ 'GitHubAPIError', 'commit_file', 'commit_files', 'create_pull_request', 'get_github_token', 'read_file' ] if __name__ == "__main__": # Self-test print("GitHub Client Self-Test") print("=" * 50) try: token = get_github_token() masked = f"{token[:7]}...{token[-4:]}" if len(token) > 11 else "***" print(f"✓ GitHub token found: {masked}") print(f" Token length: {len(token)} characters") except ValueError as e: print("✗ GitHub token not configured") print(f"\n{e}") -
__init__.py 498 B
""" Invoking GitHub Skill - GitHub API Client for Claude.ai Chat Provides programmatic GitHub operations for claude.ai chat environments where native git access isn't available. """ from .github_client import ( GitHubAPIError, commit_file, commit_files, create_pull_request, get_github_token, read_file, ) __all__ = [ 'GitHubAPIError', 'commit_file', 'commit_files', 'create_pull_request', 'get_github_token', 'read_file' ] __version__ = "1.0.0"
-
-
CHANGELOG.md 369 B
# invoking-github - Changelog All notable changes to the `invoking-github` skill are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.1.0] - 2026-09-09 ### Other - prompt-audit: dated prompting patterns across the skill catalogue (#791) - Deprecate mapping-codebases; adopt ruff 0.16.0 baseline (#747) -
README.md 297 B
# invoking-github Enables GitHub repository operations (read/write/commit/PR) for Claude.ai chat environments. Use when users request GitHub commits, repository updates, DEVLOG persistence, or cross-session state management via GitHub branches. Not needed in Claude Code (has native git access). -
SKILL.md 10.1 KB
--- name: invoking-github description: Enables GitHub repository operations (read/write/commit/PR) for Claude.ai chat environments. Use when users request GitHub commits, repository updates, DEVLOG persistence, or cross-session state management via GitHub branches. Not needed in Claude Code (has native git access). metadata: version: 0.1.0 --- # Invoking GitHub Programmatically interact with GitHub repositories from Claude.ai chat: read files, commit changes, create PRs, and persist state across sessions. ## When to Use This Skill **Not needed for:** - Claude Code environments (use native git commands) - Read-only repository access (use GitHub UI or API directly) ## Quick Start ### Prerequisites Create a GitHub Personal Access Token and add to Project Knowledge: 1. Go to https://github.com/settings/tokens 2. Create new token (classic or fine-grained) 3. Required scopes: `repo` (or `public_repo` for public repos only) 4. In Claude.ai, add to Project Knowledge: - Title: `GITHUB_API_KEY` - Content: Your token (e.g., `ghp_abc123...`) ### Single File Commit ```python from invoking_github import commit_file result = commit_file( repo="username/repo-name", path="README.md", content="# Updated README\n\nNew content here...", branch="main", message="Update README with new instructions" ) print(f"Committed: {result['commit_sha']}") ``` ### Read File ```python from invoking_github import read_file content = read_file( repo="username/repo-name", path="config.json", branch="main" ) print(content) ``` ### Batch Commit (Multiple Files) ```python from invoking_github import commit_files files = [ {"path": "src/main.py", "content": "# Python code..."}, {"path": "README.md", "content": "# Updated docs..."}, {"path": "tests/test.py", "content": "# Tests..."} ] result = commit_files( repo="username/repo-name", files=files, branch="feature-branch", message="Add new feature implementation", create_branch_from="main" # Create branch if it doesn't exist ) print(f"Committed {len(files)} files: {result['commit_sha']}") ``` ### Create Pull Request ```python from invoking_github import create_pull_request pr = create_pull_request( repo="username/repo-name", head="feature-branch", base="main", title="Add new feature", body="## Changes\n- Implemented feature X\n- Updated docs\n- Added tests" ) print(f"PR created: {pr['html_url']}") ``` ## Core Functions ### `read_file()` Read a file from repository: ```python read_file( repo: str, # "owner/name" path: str, # "path/to/file.py" branch: str = "main" # Branch name ) -> str ``` **Returns**: File content as string **Raises**: `GitHubAPIError` if file not found or access denied ### `commit_file()` Commit a single file (create or update): ```python commit_file( repo: str, # "owner/name" path: str, # "path/to/file.py" content: str, # New file content branch: str, # Target branch message: str, # Commit message create_branch_from: str = None # Create branch from this if doesn't exist ) -> dict ``` **Returns**: Dict with `commit_sha`, `branch`, `file_path` **Raises**: `GitHubAPIError` on conflicts or auth failures ### `commit_files()` Commit multiple files in a single commit: ```python commit_files( repo: str, # "owner/name" files: list[dict], # [{"path": "...", "content": "..."}] branch: str, # Target branch message: str, # Commit message create_branch_from: str = None # Create branch from this if doesn't exist ) -> dict ``` **Returns**: Dict with `commit_sha`, `branch`, `files_committed` **Raises**: `GitHubAPIError` on failures **Note**: Uses Git Trees API for efficiency - atomic commit of all files. ### `create_pull_request()` Create a pull request: ```python create_pull_request( repo: str, # "owner/name" head: str, # Source branch (your changes) base: str, # Target branch (where to merge) title: str, # PR title body: str = "" # PR description (supports markdown) ) -> dict ``` **Returns**: Dict with `number`, `html_url`, `state` **Raises**: `GitHubAPIError` if branches invalid or PR exists ## Credential Configuration This skill requires a GitHub Personal Access Token. Two configuration methods: ### Method 1: Project Knowledge (Recommended) Best for Claude.ai chat users (mobile/web): 1. Create token at https://github.com/settings/tokens 2. In Claude.ai Project settings → Add to Project Knowledge 3. Create document titled `GITHUB_API_KEY` 4. Paste your token as content **Permissions required**: - Classic token: `repo` scope - Fine-grained token: Repository permissions → Contents (read/write) and Pull requests (read/write) ### Method 2: API Credentials Skill (Fallback) Alternatively, use combined credentials file: 1. Create `API_CREDENTIALS.json` in project knowledge 2. Add: `{"github_api_key": "ghp_your-token-here"}` ## Integration with Iterating Skill Auto-persist DEVLOG.md to GitHub for cross-session continuity: ```python # In your DEVLOG update function from invoking_github import commit_file from pathlib import Path def update_devlog_with_sync(data, repo="user/project", branch="devlog"): """Update DEVLOG.md locally and sync to GitHub""" # Update local DEVLOG update_devlog(data) # Your existing function # Auto-sync to GitHub try: devlog_content = Path("DEVLOG.md").read_text() result = commit_file( repo=repo, path="DEVLOG.md", content=devlog_content, branch=branch, message=f"DEVLOG: {data['title']}", create_branch_from="main" ) print(f"✓ DEVLOG synced to GitHub ({repo}:{branch})") return result except Exception as e: print(f"⚠ DEVLOG sync failed: {e}") # Continue anyway - local DEVLOG.md still updated return None ``` **Benefits:** - Automatic backup of session progress - Cross-device access to development logs - Git history of decisions and progress - No manual copy/paste to Project Knowledge See [references/iterating-integration.md](references/iterating-integration.md) for complete patterns. ## Error Handling All functions raise `GitHubAPIError` with descriptive messages: ```python from invoking_github import commit_file, GitHubAPIError try: result = commit_file( repo="user/repo", path="file.py", content="...", branch="main", message="Update" ) except GitHubAPIError as e: if e.status_code == 404: print("Repository or branch not found") elif e.status_code == 401: print("Authentication failed - check your token") elif e.status_code == 403: print("Access denied - check token permissions") elif e.status_code == 409: print("Conflict - file was modified since last read") else: print(f"GitHub API error: {e}") ``` **Common errors:** - **404**: Repository, branch, or file not found - **401/403**: Invalid token or insufficient permissions - **409**: Merge conflict (file changed concurrently) - **422**: Validation error (invalid branch name, etc.) - **Rate limit**: Too many requests (wait before retrying) ## Best Practices 1. **Use descriptive commit messages** - Bad: "Update files" - Good: "Add authentication middleware and update user routes" 2. **Batch commits when possible** - Use `commit_files()` for related changes - Single atomic commit = cleaner git history 3. **Create feature branches** - Don't commit directly to main - Use `create_branch_from="main"` parameter - Create PR for review 4. **Secure token management** - Use fine-grained tokens with minimal scopes - Set expiration dates - Rotate regularly - Never commit tokens to repositories ## Advanced Usage ### Conditional Commits Only commit if file content changed: ```python from invoking_github import read_file, commit_file, GitHubAPIError try: current_content = read_file(repo, path, branch) if current_content != new_content: commit_file(repo, path, new_content, branch, "Update file") else: print("No changes detected, skipping commit") except GitHubAPIError as e: if e.status_code == 404: # File doesn't exist, create it commit_file(repo, path, new_content, branch, "Create file") else: raise ``` ### Session-Specific Branches Avoid conflicts with unique branch names: ```python import datetime session_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") branch_name = f"devlog-{session_id}" commit_file( repo="user/project", path="DEVLOG.md", content=devlog_content, branch=branch_name, message="Session progress", create_branch_from="main" ) ``` ### Multi-Repository Workflows Work across multiple repos: ```python repos = ["user/frontend", "user/backend", "user/docs"] for repo in repos: commit_file( repo=repo, path="VERSION", content="2.0.0\n", branch="release-2.0", message="Bump version to 2.0.0" ) ``` ## Limitations - **Target environment**: Claude.ai chat only (not Claude Code) - **No OAuth**: Must use Personal Access Tokens manually - **No git operations**: Pure REST API (no clone, pull, rebase, etc.) - **File size**: GitHub API limits ~100MB per file - **Rate limits**: 5000 requests/hour for authenticated users - **Network required**: All operations require internet access ## See Also - [references/credential-setup.md](references/credential-setup.md) - Step-by-step credential guide - [references/iterating-integration.md](references/iterating-integration.md) - DEVLOG auto-sync patterns - [references/troubleshooting.md](references/troubleshooting.md) - Common issues and solutions - [GitHub REST API Docs](https://docs.github.com/rest) - Official GitHub API reference ## Token Efficiency This skill uses ~800 tokens when loaded but provides essential GitHub operations for claude.ai chat environments where native git access isn't available. Enables persistent state management and cross-session workflows.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.