| name | github-repo-management |
| description | Clone/create/fork repos; manage remotes, releases. |
| version | 1.1.0 |
| author | Hermes Agent |
| license | MIT |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["GitHub","Repositories","Git","Releases","Secrets","Configuration"],"related_skills":["github-auth","github-pr-workflow","github-issues"]}} |
GitHub Repository Management
Create, clone, fork, configure, and manage GitHub repositories. Each section shows gh first, then the git + curl fallback.
Prerequisites
- Authenticated with GitHub (see
github-auth skill)
Setup
if command -v gh &>/dev/null && gh auth status &>/dev/null; then
AUTH="gh"
else
AUTH="git"
if [ -z "$GITHUB_TOKEN" ]; then
if [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
fi
fi
fi
if [ "$AUTH" = "gh" ]; then
GH_USER=$(gh api user --jq '.login')
else
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'])")
fi
If you're inside a repo already:
REMOTE_URL=$(git remote get-url origin)
OWNER_REPO=$(echo "$REMOTE_URL" | sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=$(echo "$OWNER_REPO" | cut -d/ -f1)
REPO=$(echo "$OWNER_REPO" | cut -d/ -f2)
1. Cloning Repositories
Cloning is pure git — works identically either way:
git clone https://github.com/owner/repo-name.git
git clone https://github.com/owner/repo-name.git ./my-local-dir
git clone --depth 1 https://github.com/owner/repo-name.git
git clone --branch develop https://github.com/owner/repo-name.git
git clone git@github.com:owner/repo-name.git
With gh (shorthand):
gh repo clone owner/repo-name
gh repo clone owner/repo-name -- --depth 1
2. Creating Repositories
With gh:
gh repo create my-new-project --public --clone
gh repo create my-new-project --private --description "A useful tool" --license MIT --clone
gh repo create my-org/my-new-project --public --clone
cd /path/to/existing/project
gh repo create my-project --source . --public --push
With git + curl:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/user/repos \
-d '{
"name": "my-new-project",
"description": "A useful tool",
"private": false,
"auto_init": true,
"license_template": "mit"
}'
git clone https://github.com/$GH_USER/my-new-project.git
cd my-new-project
cd /path/to/existing/project
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/$GH_USER/my-new-project.git
git push -u origin main
To create under an organization:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/orgs/my-org/repos \
-d '{"name": "my-new-project", "private": false}'
From a Template
With gh:
gh repo create my-new-app --template owner/template-repo --public --clone
With curl:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/owner/template-repo/generate \
-d '{"owner": "'"$GH_USER"'", "name": "my-new-app", "private": false}'
3. Forking Repositories
With gh:
gh repo fork owner/repo-name --clone
With git + curl:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/owner/repo-name/forks
sleep 3
git clone https://github.com/$GH_USER/repo-name.git
cd repo-name
git remote add upstream https://github.com/owner/repo-name.git
Keeping a Fork in Sync
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
With gh (shortcut):
gh repo sync $GH_USER/repo-name
4. Repository Information
With gh:
gh repo view owner/repo-name
gh repo list --limit 20
gh search repos "machine learning" --language python --sort stars
With curl:
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO \
| python3 -c "
import sys, json
r = json.load(sys.stdin)
print(f\"Name: {r['full_name']}\")
print(f\"Description: {r['description']}\")
print(f\"Stars: {r['stargazers_count']} Forks: {r['forks_count']}\")
print(f\"Default branch: {r['default_branch']}\")
print(f\"Language: {r['language']}\")"
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/user/repos?per_page=20&sort=updated" \
| python3 -c "
import sys, json
for r in json.load(sys.stdin):
vis = 'private' if r['private'] else 'public'
print(f\" {r['full_name']:40} {vis:8} {r.get('language', ''):10} ★{r['stargazers_count']}\")"
curl -s \
"https://api.github.com/search/repositories?q=machine+learning+language:python&sort=stars&per_page=10" \
| python3 -c "
import sys, json
for r in json.load(sys.stdin)['items']:
print(f\" {r['full_name']:40} ★{r['stargazers_count']:6} {r['description'][:60] if r['description'] else ''}\")"
5. Repository Settings
With gh:
gh repo edit --description "Updated description" --visibility public
gh repo edit --enable-wiki=false --enable-issues=true
gh repo edit --default-branch main
gh repo edit --add-topic "machine-learning,python"
gh repo edit --enable-auto-merge
With curl:
curl -s -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO \
-d '{
"description": "Updated description",
"has_wiki": false,
"has_issues": true,
"allow_auto_merge": true
}'
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.mercy-preview+json" \
https://api.github.com/repos/$OWNER/$REPO/topics \
-d '{"names": ["machine-learning", "python", "automation"]}'
6. Branch Protection
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/branches/main/protection
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/branches/main/protection \
-d '{
"required_status_checks": {
"strict": true,
"contexts": ["ci/test", "ci/lint"]
},
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 1
},
"restrictions": null
}'
7. Secrets Management (GitHub Actions)
With gh:
gh secret set API_KEY --body "your-secret-value"
gh secret set SSH_KEY < ~/.ssh/id_rsa
gh secret list
gh secret delete API_KEY
With curl:
Secrets require encryption with the repo's public key — more involved via API:
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/public-key
python3 -c "
from base64 import b64encode
from nacl import encoding, public
import json, sys
# Get the public key
key_id = '<key_id_from_above>'
public_key = '<base64_key_from_above>'
# Encrypt
sealed = public.SealedBox(
public.PublicKey(public_key.encode('utf-8'), encoding.Base64Encoder)
).encrypt('your-secret-value'.encode('utf-8'))
print(json.dumps({
'encrypted_value': b64encode(sealed).decode('utf-8'),
'key_id': key_id
}))"
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/API_KEY \
-d '<output from python script above>'
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets \
| python3 -c "
import sys, json
for s in json.load(sys.stdin)['secrets']:
print(f\" {s['name']:30} updated: {s['updated_at']}\")"
Note: 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.
8. Releases
With gh:
gh release create v1.0.0 --title "v1.0.0" --generate-notes
gh release create v2.0.0-rc1 --draft --prerelease --generate-notes
gh release create v1.0.0 ./dist/binary --title "v1.0.0" --notes "Release notes"
gh release list
gh release download v1.0.0 --dir ./downloads
With curl:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/releases \
-d '{
"tag_name": "v1.0.0",
"name": "v1.0.0",
"body": "## Changelog\n- Feature A\n- Bug fix B",
"draft": false,
"prerelease": false,
"generate_release_notes": true
}'
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/releases \
| python3 -c "
import sys, json
for r in json.load(sys.stdin):
tag = r.get('tag_name', 'no tag')
print(f\" {tag:15} {r['name']:30} {'draft' if r['draft'] else 'published'}\")"
RELEASE_ID=<id_from_create_response>
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=binary-amd64" \
--data-binary @./dist/binary-amd64
9. GitHub Actions Workflows
With gh:
gh workflow list
gh run list --limit 10
gh run view <RUN_ID>
gh run view <RUN_ID> --log-failed
gh run rerun <RUN_ID>
gh run rerun <RUN_ID> --failed
gh workflow run ci.yml --ref main
gh workflow run deploy.yml -f environment=staging
With curl:
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/workflows \
| python3 -c "
import sys, json
for w in json.load(sys.stdin)['workflows']:
print(f\" {w['id']:10} {w['name']:30} {w['state']}\")"
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?per_page=10" \
| python3 -c "
import sys, json
for r in json.load(sys.stdin)['workflow_runs']:
print(f\" Run {r['id']} {r['name']:30} {r['conclusion'] or r['status']}\")"
RUN_ID=<run_id>
curl -s -L \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs \
-o /tmp/ci-logs.zip
cd /tmp && unzip -o ci-logs.zip -d ci-logs
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun-failed-jobs
WORKFLOW_ID=<workflow_id_or_filename>
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/dispatches \
-d
10. Gists
With gh:
gh gist create script.py --public --desc "Useful script"
gh gist list
With curl:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/gists \
-d '{
"description": "Useful script",
"public": true,
"files": {
"script.py": {"content": "print(\"hello\")"}
}
}'
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/gists \
| python3 -c "
import sys, json
for g in json.load(sys.stdin):
files = ', '.join(g['files'].keys())
print(f\" {g['id']} {g['description'] or '(no desc)':40} {files}\")"
11. Undoing Commits
When asked to undo one or more commits, the choice between git revert and git reset + force push depends on repo context:
Decision Rule
| Situation | Preferred | Why |
|---|
| Solo project / de facto single committer | git reset --hard HEAD~N + git push --force-with-lease | Clean history, no revert noise |
| Shared repo with multiple contributors | git revert <sha> | Safe — no history rewrite, others don't need to rebase |
Solo-Repo Pattern (reset + force push)
git reset --hard HEAD~N
git log --oneline -3
git push --force-with-lease origin main
Force push destroys the old commits on the remote. Only use on branches where you're the sole writer. --force-with-lease checks that the remote hasn't moved since you last fetched — preventing accidental overwrite of someone else's push.
Shared-Repo Pattern (revert)
git revert HEAD
git revert <sha>
git push origin main
Revert is safe on any branch. The original commit stays in history. Use when others may have based work on the commits being undone.
Check Before Choosing
git shortlog -sne HEAD~5..HEAD
git branch -r --contains HEAD
Pitfall: Reverting a revert
If you revert a revert, the original changes come back. This is confusing in history. Reset + force push avoids this entirely — the commits just disappear.
User Preference (this repo)
On the user's solo-maintained repos (wiki-book, openmaic-pages, etc.), they prefer git reset --hard HEAD~N + git push --force-with-lease over git revert. Don't reach for revert first on these repos.
Pitfalls
Hermes skill directories should NOT have their own GitHub repos (2026-06-15)
Hermes skill directories under ~/.hermes/skills/ sometimes get git init + git push, creating a separate GitHub repo (e.g., QianJinGuo/web-content-reviewer). The user does NOT want separate repos for skills — skills live in the local filesystem and are synced to the wiki repo via rsync -av --delete ~/.hermes/skills/wiki/ ~/wiki/skills/.
Symptoms: You notice a GitHub repo matching a skill name (e.g., gh repo list shows web-content-reviewer). The skill directory at ~/.hermes/skills/<category>/<name>/ contains a .git/ subdirectory.
Fix:
- Delete the GitHub repo:
gh repo delete OWNER/<skill-name> --yes
- Remove the
.git/ from the skill directory: rm -rf ~/.hermes/skills/<category>/<name>/.git
- Verify:
gh repo list OWNER --json name --jq '.[].name' | grep <skill-name> should return nothing
Prevention: Never git init inside a Hermes skill directory. If a skill needs version control, it's tracked through the wiki repo's skills/ sync, not as an independent repo.
Quick Reference Table
| Action | gh | git + curl |
|---|
| Clone | gh repo clone o/r | git clone https://github.com/o/r.git |
| Create repo | gh repo create name --public | curl POST /user/repos |
| Fork | gh repo fork o/r --clone | curl POST /repos/o/r/forks + git clone |
| Repo info | gh repo view o/r | curl GET /repos/o/r |
| Edit settings | gh repo edit --... | curl PATCH /repos/o/r |
| Create release | gh release create v1.0 | curl POST /repos/o/r/releases |
| List workflows | gh workflow list | curl GET /repos/o/r/actions/workflows |
| Rerun CI | gh run rerun ID | curl POST /repos/o/r/actions/runs/ID/rerun |
| Set secret | gh secret set KEY | curl PUT /repos/o/r/actions/secrets/KEY (+ encryption) |