Skills lovstudio:gh-access
git clone https://github.com/lovstudio/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/lovstudio/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/gh-access" ~/.claude/skills/lovstudio-skills-lovstudio-gh-access && rm -rf "$T"
skills/gh-access/SKILL.mdlovstudio:gh-access
Grant, revoke, and audit collaborator access on private GitHub repos — by username or email, with read-only as the safe default.
Prerequisites
CLI authenticated (gh
) with a token that has:gh auth status
scope (always)repo
scope (if the target repo is org-owned; caller must be org owner or repo admin)admin:org
- The target repo exists and is accessible to the caller.
Subcommands
This skill has three modes. Pick based on the user's intent:
| User intent | Subcommand |
|---|---|
| "开权限 / share / invite / grant" | grant |
| "撤销 / remove / revoke / 踢出" | revoke |
| "谁有权限 / who has access / list" | list |
If intent is unclear, use
AskUserQuestion to disambiguate.
Workflow
Step 0: Collect inputs via AskUserQuestion
ALWAYS collect the following BEFORE touching the API:
- Target repo —
(e.g.<owner>/<repo>
). If the user is inside a git repo, pre-fill fromlovstudio/private-demo
.gh repo view --json nameWithOwner -q .nameWithOwner - Subcommand — grant / revoke / list.
- (grant/revoke only) Identifiers — a whitespace- or comma-separated list of GitHub usernames and/or email addresses. Mixed is fine.
- (grant only) Permission level — default
(read-only). Offer:pull
— read + issues + PRs (recommended default)pull
— read + can label/close issues & PRs, no code writetriage
— write access (⚠ confirm explicitly)push
/maintain
— block unless user explicitly insistsadmin
Never silently escalate. If the user just says "给他权限" without specifying level, default to
pull and state that clearly.
Step 1: Resolve identifiers → GitHub usernames
For each identifier in the list, follow this resolution chain and record the outcome per identifier (for the final summary report):
identifier → classify → resolve
Classification rule: an identifier containing
@ is treated as an email,
otherwise as a GitHub username.
Case A — looks like a username
- Verify the account exists:
gh api "users/<login>" --jq '.login' 2>/dev/null - If it returns the login → resolved as
, statuslogin
.user_ok - If the call 404s → status
. Do NOT fall back to email invite (we don't have an email). Report and skip.user_not_found
Case B — looks like an email
- Search by email:
gh api "search/users?q=<email>+in:email" --jq '.total_count, .items[0].login' - If
and a login is returned → resolved astotal_count >= 1
, statuslogin
.email_to_user - If
→ fall back to email invite path:total_count == 0- For org repos:
then add the pending member as an outside collaborator on the repo once they accept. Note: inviting directly-to-repo by email is not supported by the REST API for non-org personal repos — if the target is a personal repo, reportgh api -X POST "orgs/<org>/invitations" -f email=<email> -f role=direct_member
and ask the user to obtain the recipient's GitHub username.email_no_account - Status:
(org) oremail_invited
(personal repo).email_no_account
- For org repos:
Show the resolution table to the user before performing writes:
| Input | Type | Resolved | Status |
|---|---|---|---|
| username | | user_ok |
| | email_to_user | |
| — | email_invited (or email_no_account) | |
| username | — | user_not_found |
Ask the user to confirm before proceeding with writes. Skip
user_not_found
and email_no_account entries by default.
Step 2: Execute
grant
For each resolved username, issue a repo invitation:
gh api -X PUT "repos/<owner>/<repo>/collaborators/<login>" \ -f permission=<pull|triage|push|maintain|admin>
- Response
= invitation sent (pending until recipient accepts).201 - Response
= already a collaborator; permission was updated.204 - Response
= user not found or already pending; inspect and report.422
For org-repo email invites that resolved to
email_invited above, no
additional call is needed — the org invitation covers repo access once the
user accepts. Tell the user to remind the recipient to check their email.
revoke
gh api -X DELETE "repos/<owner>/<repo>/collaborators/<login>"
- Response
= removed (or was never a collaborator — idempotent).204 - For email-only identifiers with no resolved login: use
only if the user explicitly wants to remove from the whole org; otherwise skip and report.gh api -X DELETE "orgs/<org>/memberships/<login>"
Before executing revokes, show the list of logins that will be removed and ask for a final confirmation (revokes are visible to the recipient and can be socially awkward to reverse).
list
gh api "repos/<owner>/<repo>/collaborators?affiliation=all" \ --jq '.[] | {login, permissions}' \ --paginate
Also list pending invitations:
gh api "repos/<owner>/<repo>/invitations" --paginate \ --jq '.[] | {invitee: .invitee.login, email, permissions, created_at}'
Present as two tables: Active collaborators and Pending invitations.
Step 3: Report
Show a final summary table for grant/revoke operations:
gh-access report — <owner>/<repo> ================================= Granted (pull): alice, bobhub Invited via email: carol@startup.io (pending org invite) Skipped: typo-user (user_not_found)
Include the invitation URL the user can share manually if helpful:
https://github.com/<owner>/<repo>/invitations
Rules
- Default to
(read-only) unless the user explicitly names a higher permission. State the chosen level clearly before executing.pull - Never escalate to
/admin
without an explicit, unambiguous request — ask a confirmingmaintain
even if the user seemed to ask.AskUserQuestion - Show the resolution table before writes. Clients mistyping a username is common; showing the resolved login prevents inviting the wrong person.
- Idempotent revokes. A
on a non-collaborator is fine — don't panic.204 - Batch-friendly. A single invocation can process a long mixed list; execute resolution in parallel where possible, but keep writes sequential so partial failures are easy to report.
- Email invites only work cleanly for org repos. For personal repos without a resolved username, stop and ask the user to obtain a GitHub username from the recipient.
- Private repos only are the typical case, but this skill works on public repos too — no need to refuse.
Common gh CLI quick reference
# Who am I? What scopes do I have? gh auth status # Is this a repo I can admin? gh api "repos/<owner>/<repo>" --jq '.permissions' # Cancel a pending invitation gh api -X DELETE "repos/<owner>/<repo>/invitations/<invitation_id>" # Show org membership of a user gh api "orgs/<org>/memberships/<login>" --jq '.role, .state'