gh-repo-mirror
What this does
Given a reference repo (e.g. org/ai-workbench), produces a new repo (e.g. org/ai-skills) with:
- Identical general settings: visibility, has_issues/projects/wiki/discussions, is_template flag (caller decides), all merge methods + commit-message/title flags, delete-branch-on-merge, allow-update-branch, allow-forking, web-commit-signoff.
- Identical security-and-analysis: advanced_security, secret_scanning, push_protection, non_provider_patterns, ai_detection, validity_checks, dependabot_security_updates.
- Identical classic branch protection on
main.
- Identical access: every team that has access to the reference repo is added to the new repo with the same permission (pull/triage/push/maintain/admin); direct collaborators are invited at the same permission. Default-on; pass
--no-mirror-access to skip.
- A bundled neo-brutalist GitHub Pages docs site under
docs/ (or /, caller picks) with the caller's branding, empty skills[] and changes[] so content lands as you add it.
- Custom domain wired via
docs/CNAME (if provided) and HTTPS enforced once the cert is issued.
Quick start
Run the helper script with the reference repo + new repo name. It interviews for anything you don't pass:
.claude/skills/gh-repo-mirror/scripts/mirror-repo.zsh \
--ref-repo Invenco-Cloud-Systems-ICS/ai-workbench \
--new-repo Invenco-Cloud-Systems-ICS/ai-skills \
--description "AI agent skills library for Invenco" \
--pages-path /docs \
--custom-domain ai-skills.docs.invencocloud.com
Add --template to set is_template=true, --no-port-docs to skip the docs/Pages scaffold (a minimal README + .gitignore commit is still created so main exists for branch protection), --no-pages to skip enabling GitHub Pages entirely, --dry-run to print the plan without changing anything.
Default-on capability (suppress with a flag):
- Team + direct-collaborator access mirroring runs on every invocation. Pass
--no-mirror-access to skip. Team slugs are scoped to the new repo's org — if the reference is in a different org, only teams that already exist in the target org will resolve; the rest log a warning.
Extra capabilities (all opt-in):
--mirror-rulesets — also copy repo-level rulesets from the reference (in addition to classic branch protection).
--cname-provider cloudflare --cname-zone-id <id> — create the DNS CNAME record for the custom domain via the Cloudflare API. Needs CLOUDFLARE_API_TOKEN in env. Use --cname-provider print to just emit the record the operator should create manually in any other provider.
--bootstrap-skill <slug> — drop a placeholder skill at <slug>/SKILL.md and insert a matching entry into docs/site.js so the initial commit has real (template) content instead of empty arrays.
Workflow
- Verify gh CLI auth.
gh api user --jq .login must match an account that can access both the reference org and create repos in the target org. Switch with gh auth switch --user <handle> if wrong. If the reference repo 404s, search orgs with gh api user/orgs --jq '.[].login' and gh repo list <org>.
- Run
scripts/mirror-repo.zsh with the flags above. The script:
- Captures the reference repo's settings, branch protection, and Pages config.
- Creates the new repo (
gh repo create).
- PATCHes general settings + security flags. If
allow_forking=false PATCH returns HTTP 422 ("organization does not allow private repository forking"), drops that one flag and retries — the org default already enforces it.
- Renders templates under
docs/ (or /) with the caller's repoSlug, repoUrl, hero eyebrow, and date stamps. Copies .nojekyll, optional CNAME.
- Writes
README.md, CHANGELOG.md, .gitignore at repo root.
git init -b main, commits, pushes.
- PUTs classic branch protection mirroring the reference faithfully — including
required_status_checks (strict + contexts) and required_pull_request_reviews (code-owner reviews, approval count, dismiss-stale) where the reference sets them, not just allow_force_pushes/allow_deletions. restrictions/dismissal_restrictions/bypass actors are intentionally omitted (org-scoped, may not resolve).
- Mirrors team access (
PUT /orgs/{org}/teams/{slug}/repos/{owner}/{repo}) and direct collaborators (PUT /repos/{owner}/{repo}/collaborators/{login}) at the same permission level as the reference. Skipped if --no-mirror-access is set.
- POSTs Pages config with
source: {branch: main, path: /docs|/} and build_type: legacy.
- Background-polls for the Let's Encrypt cert (
https_certificate.state == "approved"), then PUTs https_enforced=true.
- Verify by diff. Script prints a side-by-side of settings, branch protection, teams, and direct collaborators between reference and new repo. Any drift is highlighted.
Inputs the skill asks for if not passed
- Reference repo (
<org>/<name>)
- New repo (
<org>/<name>) — org defaults to reference org
- Description
is_template: true / false
- Pages source path:
/docs (recommended — keeps repo root clean for skill dirs) or / (matches some references like amit-t/skills)
- Custom domain (optional, e.g.
ai-skills.docs.invencocloud.com)
- Port docs verbatim from bundled template, or skip docs scaffold entirely
- Hero eyebrow text for the Pages site (e.g.
INVENCO CLOUD SYSTEMS)
Templates bundled
Under templates/:
index.html — neo-brutalist hero, README card, skill grid, changelog, drawer. Placeholders: {{REPO_URL}}, {{REPO_SLUG}}, {{HERO_EYEBROW}}, {{LAST_UPDATE}}.
site.css — full design system (themes, IBM Plex Mono, hard borders, offset shadows). Verbatim copy.
site.js — render logic with empty-state handling. Placeholders: {{REPO_URL}}, {{REPO_SLUG}}.
nojekyll — empty file copied as .nojekyll.
README.md.tmpl, CHANGELOG.md.tmpl, gitignore.tmpl — repo-root scaffolding with the same placeholders.
Known gotchas
See REFERENCE.md for: multi-account gh auth, the allow_forking=false 422, async HTTPS cert provisioning, Pages-path tradeoff, .nojekyll requirement, private-repo Pages requires GitHub Enterprise, ruleset mirror payload trimming, Cloudflare DNS API auth.
After the skill runs
You should see:
- Repo URL printed.
- Pages URL printed (custom domain or
<org>.github.io/<name>).
- A line stating whether HTTPS enforcement is enforced (cert was ready) or pending (background poll is armed; will flip when the cert lands — usually a few minutes).
- A diff block showing zero drift between reference and new repo (modulo intentional differences like
is_template).
If HTTPS enforcement is pending and the session ends, re-run:
.claude/skills/gh-repo-mirror/scripts/mirror-repo.zsh --enforce-https-only <org>/<name>
1---2name: gh-repo-mirror3description: Scaffold a new GitHub repo that mirrors an existing reference repo's general settings, branch-protection rules, security flags, and (optionally) its GitHub Pages site — porting the static HTML/CSS/JS docs and rebranding them. Use when the user says "create a new repo like X", "mirror settings from Y", "new repo same as Z with Pages", or asks for a skills-library / Pages-site scaffold modelled on another repo.4---56# gh-repo-mirror78## What this does910Given a **reference repo** (e.g. `org/ai-workbench`), produces a **new repo** (e.g. `org/ai-skills`) with:11121. Identical general settings: visibility, has_issues/projects/wiki/discussions, is_template flag (caller decides), all merge methods + commit-message/title flags, delete-branch-on-merge, allow-update-branch, allow-forking, web-commit-signoff.132. Identical security-and-analysis: advanced_security, secret_scanning, push_protection, non_provider_patterns, ai_detection, validity_checks, dependabot_security_updates.143. Identical classic branch protection on `main`.154. Identical access: every team that has access to the reference repo is added to the new repo with the same permission (pull/triage/push/maintain/admin); direct collaborators are invited at the same permission. Default-on; pass `--no-mirror-access` to skip.165. A bundled neo-brutalist GitHub Pages docs site under `docs/` (or `/`, caller picks) with the caller's branding, empty `skills[]` and `changes[]` so content lands as you add it.176. Custom domain wired via `docs/CNAME` (if provided) and HTTPS enforced once the cert is issued.1819## Quick start2021Run the helper script with the reference repo + new repo name. It interviews for anything you don't pass:2223```bash24.claude/skills/gh-repo-mirror/scripts/mirror-repo.zsh \25 --ref-repo Invenco-Cloud-Systems-ICS/ai-workbench \26 --new-repo Invenco-Cloud-Systems-ICS/ai-skills \27 --description "AI agent skills library for Invenco" \28 --pages-path /docs \29 --custom-domain ai-skills.docs.invencocloud.com30```3132Add `--template` to set `is_template=true`, `--no-port-docs` to skip the docs/Pages scaffold (a minimal README + `.gitignore` commit is still created so `main` exists for branch protection), `--no-pages` to skip enabling GitHub Pages entirely, `--dry-run` to print the plan without changing anything.3334**Default-on capability** (suppress with a flag):3536- Team + direct-collaborator access mirroring runs on every invocation. Pass `--no-mirror-access` to skip. Team slugs are scoped to the new repo's org — if the reference is in a different org, only teams that already exist in the target org will resolve; the rest log a warning.3738**Extra capabilities** (all opt-in):3940- `--mirror-rulesets` — also copy repo-level rulesets from the reference (in addition to classic branch protection).41- `--cname-provider cloudflare --cname-zone-id <id>` — create the DNS CNAME record for the custom domain via the Cloudflare API. Needs `CLOUDFLARE_API_TOKEN` in env. Use `--cname-provider print` to just emit the record the operator should create manually in any other provider.42- `--bootstrap-skill <slug>` — drop a placeholder skill at `<slug>/SKILL.md` and insert a matching entry into `docs/site.js` so the initial commit has real (template) content instead of empty arrays.4344## Workflow45461. **Verify gh CLI auth.** `gh api user --jq .login` must match an account that can access **both** the reference org and create repos in the target org. Switch with `gh auth switch --user <handle>` if wrong. If the reference repo 404s, search orgs with `gh api user/orgs --jq '.[].login'` and `gh repo list <org>`.472. **Run `scripts/mirror-repo.zsh`** with the flags above. The script:48 - Captures the reference repo's settings, branch protection, and Pages config.49 - Creates the new repo (`gh repo create`).50 - PATCHes general settings + security flags. If `allow_forking=false` PATCH returns HTTP 422 ("organization does not allow private repository forking"), drops that one flag and retries — the org default already enforces it.51 - Renders templates under `docs/` (or `/`) with the caller's `repoSlug`, `repoUrl`, hero eyebrow, and date stamps. Copies `.nojekyll`, optional `CNAME`.52 - Writes `README.md`, `CHANGELOG.md`, `.gitignore` at repo root.53 - `git init -b main`, commits, pushes.54 - PUTs classic branch protection mirroring the reference faithfully — including `required_status_checks` (strict + contexts) and `required_pull_request_reviews` (code-owner reviews, approval count, dismiss-stale) where the reference sets them, not just `allow_force_pushes`/`allow_deletions`. `restrictions`/`dismissal_restrictions`/bypass actors are intentionally omitted (org-scoped, may not resolve).55 - Mirrors team access (`PUT /orgs/{org}/teams/{slug}/repos/{owner}/{repo}`) and direct collaborators (`PUT /repos/{owner}/{repo}/collaborators/{login}`) at the same permission level as the reference. Skipped if `--no-mirror-access` is set.56 - POSTs Pages config with `source: {branch: main, path: /docs|/}` and `build_type: legacy`.57 - Background-polls for the Let's Encrypt cert (`https_certificate.state == "approved"`), then PUTs `https_enforced=true`.583. **Verify by diff.** Script prints a side-by-side of settings, branch protection, teams, and direct collaborators between reference and new repo. Any drift is highlighted.5960## Inputs the skill asks for if not passed6162- Reference repo (`<org>/<name>`)63- New repo (`<org>/<name>`) — org defaults to reference org64- Description65- `is_template`: true / false66- Pages source path: `/docs` (recommended — keeps repo root clean for skill dirs) or `/` (matches some references like `amit-t/skills`)67- Custom domain (optional, e.g. `ai-skills.docs.invencocloud.com`)68- Port docs verbatim from bundled template, or skip docs scaffold entirely69- Hero eyebrow text for the Pages site (e.g. `INVENCO CLOUD SYSTEMS`)7071## Templates bundled7273Under `templates/`:7475- `index.html` — neo-brutalist hero, README card, skill grid, changelog, drawer. Placeholders: `{{REPO_URL}}`, `{{REPO_SLUG}}`, `{{HERO_EYEBROW}}`, `{{LAST_UPDATE}}`.76- `site.css` — full design system (themes, IBM Plex Mono, hard borders, offset shadows). Verbatim copy.77- `site.js` — render logic with empty-state handling. Placeholders: `{{REPO_URL}}`, `{{REPO_SLUG}}`.78- `nojekyll` — empty file copied as `.nojekyll`.79- `README.md.tmpl`, `CHANGELOG.md.tmpl`, `gitignore.tmpl` — repo-root scaffolding with the same placeholders.8081## Known gotchas8283See [REFERENCE.md](REFERENCE.md) for: multi-account gh auth, the `allow_forking=false` 422, async HTTPS cert provisioning, Pages-path tradeoff, `.nojekyll` requirement, private-repo Pages requires GitHub Enterprise, ruleset mirror payload trimming, Cloudflare DNS API auth.8485## After the skill runs8687You should see:8889- Repo URL printed.90- Pages URL printed (custom domain or `<org>.github.io/<name>`).91- A line stating whether HTTPS enforcement is **enforced** (cert was ready) or **pending** (background poll is armed; will flip when the cert lands — usually a few minutes).92- A diff block showing zero drift between reference and new repo (modulo intentional differences like `is_template`).9394If HTTPS enforcement is pending and the session ends, re-run:9596```bash97.claude/skills/gh-repo-mirror/scripts/mirror-repo.zsh --enforce-https-only <org>/<name>98```