{“content”:”---\nname: github-repo-management\ndescription: Clone, create, fork, configure, and manage GitHub repositories. Manage remotes, secrets, releases, and workflows. Works with gh CLI or falls back to git + GitHub REST API via curl.\nversion: 1.1.0\nauthor: Hermes Agent\nlicense: MIT\nmetadata:\n hermes:\n tags: [GitHub, Repositories, Git, Releases, Secrets, Configuration]\n related_skills: [github-auth, github-pr-workflow, github-issues]\n---\n\n# GitHub Repository Management\n\nCreate, clone, fork, configure, and manage GitHub repositories. Each section shows gh first, then the git + curl fallback.\n\n## Prerequisites\n\n- Authenticated with GitHub (see github-auth skill)\n\n### Setup\n\nbash\nif command -v gh &>/dev/null && gh auth status &>/dev/null; then\n AUTH=\"gh\"\nelse\n AUTH=\"git\"\n if [ -z \"$GITHUB_TOKEN\" ]; then\n if [ -f ~/.hermes/.env ] && grep -q \"^GITHUB_TOKEN=\" ~/.hermes/.env; then\n GITHUB_TOKEN=$(grep \"^GITHUB_TOKEN=\" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\\n\\r')\n elif grep -q \"github.com\" ~/.git-credentials 2>/dev/null; then\n GITHUB_TOKEN=$(grep \"github.com\" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\\([^@]*\\)@.*|\\1|')\n fi\n fi\nfi\n\n# Get your GitHub username (needed for several operations)\nif [ \"$AUTH\" = \"gh\" ]; then\n GH_USER=$(gh api user --jq '.login')\nelse\n GH_USER=$(curl -s -H \"Authorization: token $GITHUB_TOKEN\" https://api.github.com/user | python3 -c \"import sys,json; print(json.load(sys.stdin)['login'])\")\nfi\n\n\nIf you’re inside a repo already:\n\nbash\nREMOTE_URL=$(git remote get-url origin)\nOWNER_REPO=$(echo \"$REMOTE_URL\" | sed -E 's|.*github\\.com[:/]||; s|\\.git$||')\nOWNER=$(echo \"$OWNER_REPO\" | cut -d/ -f1)\nREPO=$(echo \"$OWNER_REPO\" | cut -d/ -f2)\n\n\n---\n\n## 1. Cloning Repositories\n\nCloning is pure git — works identically either way:\n\nbash\n# Clone via HTTPS (works with credential helper or token-embedded URL)\ngit clone https://github.com/owner/repo-name.git\n\n# Clone into a specific directory\ngit clone https://github.com/owner/repo-name.git ./my-local-dir\n\n# Shallow clone (faster for large repos)\ngit clone --depth 1 https://github.com/owner/repo-name.git\n\n# Clone a specific branch\ngit clone --branch develop https://github.com/owner/repo-name.git\n\n# Clone via SSH (if SSH is configured)\ngit clone git@github.com:owner/repo-name.git\n\n\nWith gh (shorthand):\n\nbash\ngh repo clone owner/repo-name\ngh repo clone owner/repo-name -- --depth 1\n\n\n## 2. Creating Repositories\n\nWith gh:\n\nbash\n# Create a public repo and clone it\ngh repo create my-new-project --public --clone\n\n# Private, with description and license\ngh repo create my-new-project --private --description \"A useful tool\" --license MIT --clone\n\n# Under an organization\ngh repo create my-org/my-new-project --public --clone\n\n# From existing local directory\ncd /path/to/existing/project\ngh repo create my-project --source . --public --push\n\n\nWith git + curl:\n\nbash\n# Create the remote repo via API\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/user/repos \\\n -d '{\n \"name\": \"my-new-project\",\n \"description\": \"A useful tool\",\n \"private\": false,\n \"auto_init\": true,\n \"license_template\": \"mit\"\n }'\n\n# Clone it\ngit clone https://github.com/$GH_USER/my-new-project.git\ncd my-new-project\n\n# -- OR -- push an existing local directory to the new repo\ncd /path/to/existing/project\ngit init\ngit add .\ngit commit -m \"Initial commit\"\ngit remote add origin https://github.com/$GH_USER/my-new-project.git\ngit push -u origin main\n\n\nTo create under an organization:\n\nbash\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/orgs/my-org/repos \\\n -d '{\"name\": \"my-new-project\", \"private\": false}'\n\n\n### From a Template\n\nWith gh:\n\nbash\ngh repo create my-new-app --template owner/template-repo --public --clone\n\n\nWith curl:\n\nbash\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/owner/template-repo/generate \\\n -d '{\"owner\": \"'\"$GH_USER\"'\", \"name\": \"my-new-app\", \"private\": false}'\n\n\n## 3. Forking Repositories\n\nWith gh:\n\nbash\ngh repo fork owner/repo-name --clone\n\n\nWith git + curl:\n\nbash\n# Create the fork via API\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/owner/repo-name/forks\n\n# Wait a moment for GitHub to create it, then clone\nsleep 3\ngit clone https://github.com/$GH_USER/repo-name.git\ncd repo-name\n\n# Add the original repo as \"upstream\" remote\ngit remote add upstream https://github.com/owner/repo-name.git\n\n\n### Keeping a Fork in Sync\n\nbash\n# Pure git — works everywhere\ngit fetch upstream\ngit checkout main\ngit merge upstream/main\ngit push origin main\n\n\nWith gh (shortcut):\n\nbash\ngh repo sync $GH_USER/repo-name\n\n\n## 4. Repository Information\n\nWith gh:\n\nbash\ngh repo view owner/repo-name\ngh repo list --limit 20\ngh search repos \"machine learning\" --language python --sort stars\n\n\nWith curl:\n\nbash\n# View repo details\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO \\\n | python3 -c \"\nimport sys, json\nr = json.load(sys.stdin)\nprint(f\\\"Name: {r['full_name']}\\\")\nprint(f\\\"Description: {r['description']}\\\")\nprint(f\\\"Stars: {r['stargazers_count']} Forks: {r['forks_count']}\\\")\nprint(f\\\"Default branch: {r['default_branch']}\\\")\nprint(f\\\"Language: {r['language']}\\\")\"\n\n# List your repos\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n \"https://api.github.com/user/repos?per_page=20&sort=updated\" \\\n | python3 -c \"\nimport sys, json\nfor r in json.load(sys.stdin):\n vis = 'private' if r['private'] else 'public'\n print(f\\\" {r['full_name']:40} {vis:8} {r.get('language', ''):10} ★{r['stargazers_count']}\\\")\"\n\n# Search repos\ncurl -s \\\n \"https://api.github.com/search/repositories?q=machine+learning+language:python&sort=stars&per_page=10\" \\\n | python3 -c \"\nimport sys, json\nfor r in json.load(sys.stdin)['items']:\n print(f\\\" {r['full_name']:40} ★{r['stargazers_count']:6} {r['description'][:60] if r['description'] else ''}\\\")\"\n\n\n## 5. Repository Settings\n\nWith gh:\n\nbash\ngh repo edit --description \"Updated description\" --visibility public\ngh repo edit --enable-wiki=false --enable-issues=true\ngh repo edit --default-branch main\ngh repo edit --add-topic \"machine-learning,python\"\ngh repo edit --enable-auto-merge\n\n\nWith curl:\n\nbash\ncurl -s -X PATCH \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO \\\n -d '{\n \"description\": \"Updated description\",\n \"has_wiki\": false,\n \"has_issues\": true,\n \"allow_auto_merge\": true\n }'\n\n# Update topics\ncurl -s -X PUT \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n -H \"Accept: application/vnd.github.mercy-preview+json\" \\\n https://api.github.com/repos/$OWNER/$REPO/topics \\\n -d '{\"names\": [\"machine-learning\", \"python\", \"automation\"]}'\n\n\n## 6. Branch Protection\n\nbash\n# View current protection\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/branches/main/protection\n\n# Set up branch protection\ncurl -s -X PUT \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/branches/main/protection \\\n -d '{\n \"required_status_checks\": {\n \"strict\": true,\n \"contexts\": [\"ci/test\", \"ci/lint\"]\n },\n \"enforce_admins\": false,\n \"required_pull_request_reviews\": {\n \"required_approving_review_count\": 1\n },\n \"restrictions\": null\n }'\n\n\n## 7. Secrets Management (GitHub Actions)\n\nWith gh:\n\nbash\ngh secret set API_KEY --body \"your-secret-value\"\ngh secret set SSH_KEY < ~/.ssh/id_rsa\ngh secret list\ngh secret delete API_KEY\n\n\nWith curl:\n\nSecrets require encryption with the repo’s public key — more involved via API:\n\nbash\n# Get the repo's public key for encrypting secrets\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/secrets/public-key\n\n# Encrypt and set (requires Python with PyNaCl)\npython3 -c \"\nfrom base64 import b64encode\nfrom nacl import encoding, public\nimport json, sys\n\n# Get the public key\nkey_id = '<key_id_from_above>'\npublic_key = '<base64_key_from_above>'\n\n# Encrypt\nsealed = public.SealedBox(\n public.PublicKey(public_key.encode('utf-8'), encoding.Base64Encoder)\n).encrypt('your-secret-value'.encode('utf-8'))\nprint(json.dumps({\n 'encrypted_value': b64encode(sealed).decode('utf-8'),\n 'key_id': key_id\n}))\"\n\n# Then PUT the encrypted secret\ncurl -s -X PUT \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/secrets/API_KEY \\\n -d '<output from python script above>'\n\n# List secrets (names only, values hidden)\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/secrets \\\n | python3 -c \"\nimport sys, json\nfor s in json.load(sys.stdin)['secrets']:\n print(f\\\" {s['name']:30} updated: {s['updated_at']}\\\")\"\n\n\nNote: For secrets, gh secret set is dramatically simpler. If setting secrets is needed and gh isn’t available, recommend installing it for just that operation.\n\n## 8. Releases\n\nWith gh:\n\nbash\ngh release create v1.0.0 --title \"v1.0.0\" --generate-notes\ngh release create v2.0.0-rc1 --draft --prerelease --generate-notes\ngh release create v1.0.0 ./dist/binary --title \"v1.0.0\" --notes \"Release notes\"\ngh release list\ngh release download v1.0.0 --dir ./downloads\n\n\nWith curl:\n\nbash\n# Create a release\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/releases \\\n -d '{\n \"tag_name\": \"v1.0.0\",\n \"name\": \"v1.0.0\",\n \"body\": \"## Changelog\\n- Feature A\\n- Bug fix B\",\n \"draft\": false,\n \"prerelease\": false,\n \"generate_release_notes\": true\n }'\n\n# List releases\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/releases \\\n | python3 -c \"\nimport sys, json\nfor r in json.load(sys.stdin):\n tag = r.get('tag_name', 'no tag')\n print(f\\\" {tag:15} {r['name']:30} {'draft' if r['draft'] else 'published'}\\\")\"\n\n# Upload a release asset (binary file)\nRELEASE_ID=<id_from_create_response>\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n -H \"Content-Type: application/octet-stream\" \\\n \"https://uploads.github.com/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=binary-amd64\" \\\n --data-binary @./dist/binary-amd64\n\n\n## 9. GitHub Actions Workflows\n\nWith gh:\n\nbash\ngh workflow list\ngh run list --limit 10\ngh run view <RUN_ID>\ngh run view <RUN_ID> --log-failed\ngh run rerun <RUN_ID>\ngh run rerun <RUN_ID> --failed\ngh workflow run ci.yml --ref main\ngh workflow run deploy.yml -f environment=staging\n\n\nWith curl:\n\nbash\n# List workflows\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/workflows \\\n | python3 -c \"\nimport sys, json\nfor w in json.load(sys.stdin)['workflows']:\n print(f\\\" {w['id']:10} {w['name']:30} {w['state']}\\\")\"\n\n# List recent runs\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n \"https://api.github.com/repos/$OWNER/$REPO/actions/runs?per_page=10\" \\\n | python3 -c \"\nimport sys, json\nfor r in json.load(sys.stdin)['workflow_runs']:\n print(f\\\" Run {r['id']} {r['name']:30} {r['conclusion'] or r['status']}\\\")\"\n\n# Download failed run logs\nRUN_ID=<run_id>\ncurl -s -L \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs \\\n -o /tmp/ci-logs.zip\ncd /tmp && unzip -o ci-logs.zip -d ci-logs\n\n# Re-run a failed workflow\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun\n\n# Re-run only failed jobs\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun-failed-jobs\n\n# Trigger a workflow manually (workflow_dispatch)\nWORKFLOW_ID=<workflow_id_or_filename>\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/dispatches \\\n -d '{\"ref\": \"main\", \"inputs\": {\"environment\": \"staging\"}}'\n\n\n## 10. Gists\n\nWith gh:\n\nbash\ngh gist create script.py --public --desc \"Useful script\"\ngh gist list\n\n\nWith curl:\n\nbash\n# Create a gist\ncurl -s -X POST \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/gists \\\n -d '{\n \"description\": \"Useful script\",\n \"public\": true,\n \"files\": {\n \"script.py\": {\"content\": \"print(\\\"hello\\\")\"}\n }\n }'\n\n# List your gists\ncurl -s \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n https://api.github.com/gists \\\n | python3 -c \"\nimport sys, json\nfor g in json.load(sys.stdin):\n files = ', '.join(g['files'].keys())\n print(f\\\" {g['id']} {g['description'] or '(no desc)':40} {files}\\\")\"\n\n\n## 11. Deleting Files and Directories (Contents API)\n\nDelete a file (via curl — Contents API only works on files, not directories):\n\nbash\n# Step 1: Get the SHA of the file\ncurl -s -X GET \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n \"https://api.github.com/repos/$OWNER/$REPO/contents/$PATH_TO_FILE\"\n\n# Step 2: Delete the file\ncurl -s -X DELETE \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n \"https://api.github.com/repos/$OWNER/$REPO/contents/$PATH_TO_FILE\" \\\n -d '{\n \"message\": \"chore: remove unwanted file\",\n \"sha\": \"$FILE_SHA\"\n }'\n\n\nDelete a directory: The Contents API has no dedicated directory endpoint. The workaround — delete the last file inside the directory. GitHub automatically removes the empty parent directory.\n\nbash\n# Get the root tree SHA first\nROOT_SHA=$(curl -s -X GET \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n \"https://api.github.com/repos/$OWNER/$REPO/git/trees/main?recursive=1\" \\\n | python3 -c \"\nimport sys, json\nd = json.load(sys.stdin)\nfor item in d['tree']:\n if item['path'] == 'my-directory':\n print(item['sha'])\n break\")\n\n# Get SHA of the file inside the directory (get root tree, find the file path)\n# Then delete the file — parent dir disappears automatically\ncurl -s -X DELETE \\\n -H \"Authorization: token $GITHUB_TOKEN\" \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n \"https://api.github.com/repos/$OWNER/$REPO/contents/my-directory/some-file.js\" \\\n -d '{\"message\": \"chore: remove my-directory\", \"sha\": \"$FILE_SHA\"}'\n\n\n**⚠️ Pitfall:** Deleting a directory via DELETE /repos/o/r/contents/dir-path returns 422 \"dir is not a file\". Always target the file inside.\n\n## Quick Reference Table\n\n| Action | gh | git + curl |\n|--------|-----|-----------|\n| Clone | gh repo clone o/r | git clone https://github.com/o/r.git |\n| Create repo | gh repo create name --public | curl POST /user/repos |\n| Fork | gh repo fork o/r --clone | curl POST /repos/o/r/forks + git clone |\n| Repo info | gh repo view o/r | curl GET /repos/o/r |\n| Edit settings | gh repo edit --... | curl PATCH /repos/o/r |\n| Create release | gh release create v1.0 | curl POST /repos/o/r/releases |\n| List workflows | gh workflow list | curl GET /repos/o/r/actions/workflows |\n| Rerun CI | gh run rerun ID | curl POST /repos/o/r/actions/runs/ID/rerun |\n| Delete file | N/A | curl DELETE /repos/o/r/contents/path + sha |\n| Delete directory | N/A | Delete last file inside; parent dir auto-removes |\n| Set secret | gh secret set KEY | curl PUT /repos/o/r/actions/secrets/KEY (+ encryption) |\n”}