Skip to content

lipex360x/github-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

github-toolkit

version Bash GitHub CLI jq gum bats-core shellcheck

Standalone GitHub project setup/teardown scripts. Create repos, apply standard labels and project boards, bootstrap milestones and issues from local JSON fixtures, and tear down or nuke the whole thing for re-testing — all from any working directory.

Extracted from pi-cli and adapted to run anywhere via a single dispatcher entrypoint.

Contents


Layout

github-toolkit/
├── install.sh              ← installer (symlink into PATH dir)
├── bin/
│   └── gh-toolkit          ← single entrypoint (dispatcher; resolves symlinks)
├── scripts/                ← script implementations (12 commands + 1 sourced lib)
│   ├── create-repo.sh
│   ├── setup-labels.sh
│   ├── setup-board.sh
│   ├── create-milestones.sh
│   ├── create-issues.sh
│   ├── bootstrap-issues.sh
│   ├── bootstrap-milestones.sh
│   ├── apply-labels.sh
│   ├── find-project.sh
│   ├── gh-preflight.sh     ← sourced library, not standalone
│   ├── teardown-repo.sh
│   ├── nuke-project.sh
│   └── release.sh
├── lib/
│   └── log.sh              ← logging library (shared)
├── tests/                  ← bats-core test files (*.bats — 265 tests)
├── SKILL.md                ← orchestration spec for AI agents
└── README.md

Install

The toolkit ships with install.sh, which symlinks bin/gh-toolkit into a directory on your PATH. Default target is /usr/local/bin (in macOS default PATH — no shell config edit required).

cd ~/www/claude/projects/github-toolkit
./install.sh                         # → CREATED:link/usr/local/bin/gh-toolkit (sudo if needed)
./install.sh --dry-run               # preview without changes
./install.sh --target-dir ~/bin      # user-local target (no sudo, must be in PATH)
./install.sh --force                 # replace stale link pointing elsewhere
./install.sh --uninstall             # remove the symlink

Verify:

gh-toolkit --list   # shows 10 commands

install.sh is idempotent — safe to re-run. Output uses the same CREATED/EXISTS/REPLACED/DELETED/SKIPPED/DRY_RUN/FAILED contract as the rest of the toolkit.

Note

If you don't want a symlink, run via absolute path: ~/www/claude/projects/github-toolkit/bin/gh-toolkit <command>. Or add bin/ to PATH in your shell config — both work.

Optional alias for shorter typing:

alias ght='gh-toolkit'

Project root resolution

Scripts produce/consume local artifacts in <project-root>/:

  • dev/fixtures/milestones.json, dev/fixtures/bootstrap-issues.json — bootstrap output
  • dev/fixtures/<slug>/, dev/checklist-<slug>.md — created/destroyed by start-new-project flow
  • .logs/scripts/<name>.log — script logs (stderr + file)
  • .config/settings.json — optional, read by gh-preflight for rate-limit threshold

Resolution order:

  1. $GH_PROJECT_ROOT if set
  2. $(pwd) (current working directory)
# Default — runs from inside your project directory
cd ~/www/myproj
gh-toolkit create-repo lipex360/myproj --private

# Override — operate on another project from anywhere
GH_PROJECT_ROOT=~/www/other-proj gh-toolkit setup-labels lipex360/other-proj

Note

GitHub-only operations (create-repo, setup-labels, setup-board, create-milestones, create-issues, teardown-repo) work regardless of cwd<owner/repo> is always explicit and gh is global. Only the local-artifact steps care about PROJECT_ROOT.


Quick start — full setup flow

cd ~/www/myproj   # or set GH_PROJECT_ROOT

# Step 0 — build local fixtures
gh-toolkit bootstrap-milestones --init
gh-toolkit bootstrap-milestones "Backlog" --description "deferred work"
gh-toolkit bootstrap-milestones "MVP"     --description "first release" --due "2026-08-01"

gh-toolkit bootstrap-issues --init
gh-toolkit bootstrap-issues "Setup auth"           --labels "feat,P1,M" --body "implement OAuth login flow" --milestone "MVP"
gh-toolkit bootstrap-issues "Investigate idle bug" --labels "fix,P2,S"  --body-file dev/issue-bodies/idle-investigation.md --milestone "Backlog"

# Step 1 — create repo
gh-toolkit create-repo lipex360/myproj --private --description "..."

# Step 2 — labels, board, milestones (any order)
gh-toolkit setup-labels      lipex360/myproj
gh-toolkit setup-board       lipex360/myproj
gh-toolkit create-milestones lipex360/myproj dev/fixtures/milestones.json

# Step 3 — issues (filename created by bootstrap-issues)
gh-toolkit create-issues lipex360/myproj dev/fixtures/bootstrap-issues.json

Issue body — inline vs body-file

Issues aceitam corpo (markdown) de duas formas, mutuamente exclusivas:

Forma Quando usar Como passar
Inline (--body "...") Issues curtas: uma frase, descrição simples, link único. gh-toolkit bootstrap-issues "title" --labels "..." --body "implementar X"
Body-file (--body-file <path>) Issues longas: checklists, code blocks, múltiplas seções, RFCs, especificações detalhadas. gh-toolkit bootstrap-issues "title" --labels "..." --body-file dev/issue-bodies/foo.md

Regras:

  • Se ambos forem passados → falha com FAILED:issue/<title>:--body and --body-file are mutually exclusive.
  • O caminho do body-file é registrado no JSON como body_file e só lido no momento de create-issues. Se o arquivo for renomeado/deletado entre o bootstrap e o create, a issue falha com FAILED:issue/<title>:body_file not found:<path>.
  • Caminhos relativos são resolvidos contra PROJECT_ROOT. Use absolutos se preferir não depender de cwd.

Estrutura sugerida para body-files

<project-root>/
  dev/
    issue-bodies/
      auth-setup.md
      db-migration.md
      idle-investigation.md
    fixtures/
      bootstrap-issues.json   ← referencia "body_file": "dev/issue-bodies/auth-setup.md"
      milestones.json

Editando o JSON de issues à mão

Você pode também montar dev/fixtures/bootstrap-issues.json direto, sem usar bootstrap-issues:

[
  {
    "title": "Setup auth",
    "body": "implement OAuth login flow",
    "labels": ["feat", "P1", "M"],
    "milestone": "MVP"
  },
  {
    "title": "Migrate DB schema",
    "body_file": "dev/issue-bodies/db-migration.md",
    "labels": ["feat", "P1", "L"],
    "milestone": "MVP"
  }
]

create-issues aceita esse JSON direto:

gh-toolkit create-issues lipex360/myproj dev/fixtures/bootstrap-issues.json

Apply labels to existing issues

setup-labels cria as definições das 14 labels padrão no repo. create-issues aplica labels na criação. Para issues que já existem (criadas via UI, gh issue create, ou importadas), use apply-labels.

Dois modos:

Inline — single issue

# Adicionar
gh-toolkit apply-labels lipex360/myproj 12 --add "feat,P1"

# Remover
gh-toolkit apply-labels lipex360/myproj 12 --remove "chore"

# Combinar
gh-toolkit apply-labels lipex360/myproj 12 --add "feat,P1" --remove "chore"

# Preview
gh-toolkit apply-labels lipex360/myproj 12 --add "feat,P1" --dry-run

JSON — bulk via fixture

gh-toolkit apply-labels lipex360/myproj dev/fixtures/relabel.json

Fixture format:

[
  {"number": 12, "add": ["feat", "P1"], "remove": ["chore"]},
  {"number": 15, "add": ["fix", "M"]},
  {"number": 22, "remove": ["wontfix"]}
]

Saída

UPDATED:issue/12:+feat,+P1,-chore     # mudou
SKIPPED:issue/15:already applied       # nada a fazer (idempotente)
DRY_RUN:issue/22:+fix,+M               # com --dry-run
FAILED:issue/30:label not found in repo: ghost-label

Garantias

  • Idempotente: lê labels atuais via gh issue view, calcula delta, só chama gh issue edit quando há mudança real. Re-rodar é seguro.
  • Validação upfront: o script faz uma chamada gh label list para o repo e valida que todas as labels do --add/--remove existem antes de tentar editar. Label não cadastrada → entrada falha sem chamar issue edit.
  • Continuação em erro (JSON mode): uma entrada inválida não impede as outras. Exit code reflete se houve qualquer falha (1) ou tudo OK (0).

Tip

Se você renomeou ou adicionou uma label nova depois que issues já existiam, apply-labels é o caminho. Combine com gh issue list --json number,labels + jq para gerar a fixture programaticamente.


Release — bump, tag, publish

Project-agnostic. Funciona em qualquer projeto que use CHANGELOG.md como source-of-truth da versão. Lê o topo do CHANGELOG (## [vX.Y.Z] ou ## [X.Y.Z]), atualiza manifests detectados automaticamente, commita, dá push, e opcionalmente cria tag + GitHub Release.

Bootstrap — primeiro CHANGELOG

Para projetos novos sem CHANGELOG.md:

gh-toolkit release --init             # cria CHANGELOG.md com template v0.1.0
gh-toolkit release --init --dry-run   # preview do conteúdo
gh-toolkit release --init --force     # sobrescreve se já existir

Template gerado segue Keep a Changelog com entrada inicial ## [v0.1.0] — <today>. Após gerado, o arquivo já é parseable por release (sem --init) — o próximo gh-toolkit release lê v0.1.0 e bumpa manifests.

--init é mutuamente exclusivo com --publish (é bootstrap, não release).

Modos

# Bump + commit + push (sem tag, sem release)
gh-toolkit release

# Bump + commit + push + tag + GitHub Release
gh-toolkit release --publish

# Preview de tudo (zero changes)
gh-toolkit release --dry-run [--publish]

Auto-detect de manifests

O script atualiza estes arquivos quando existem (ignora silenciosamente se não):

Arquivo Pattern atualizado
package.json "version": "..." no top-level
pyproject.toml version = "..." no início (qualquer seção [project]/[tool.poetry])
Cargo.toml version = "..." apenas em [package] (deixa [dependencies] intactas)
VERSION conteúdo do arquivo (single-line)
README.md badge shields.io/badge/version-vX-<color>

Overrides

gh-toolkit release --changelog HISTORY.md          # CHANGELOG em outro path
gh-toolkit release --no-readme-badge                # pula update do badge
gh-toolkit release --no-push                        # commita mas não dá push
gh-toolkit release --prefix "release-"              # tag = "release-1.2.3" em vez de "v1.2.3"
gh-toolkit release --remote upstream                # push para outro remote

Saída

UPDATED:file/package.json:0.1.0→0.2.0
UPDATED:file/README.md:badge 0.1.0→0.2.0
SKIPPED:file/pyproject.toml:up to date (0.2.0)
CREATED:commit/abc1234
PUSHED:remote/origin/main
CREATED:tag/v0.2.0
PUSHED:tag/v0.2.0
CREATED:release/v0.2.0

Fluxo recomendado

# 1. Editar CHANGELOG.md adicionando ## [v0.2.0] no topo com as notes
$EDITOR CHANGELOG.md
git add CHANGELOG.md && git commit -m "docs: changelog v0.2.0"

# 2. Preview do release
gh-toolkit release --dry-run --publish

# 3. Se OK, executar
gh-toolkit release --publish

Garantias

  • Idempotente: manifest já na versão correta → SKIPPED:up to date. Re-rodar é seguro.
  • Sem mexer onde não deve: Cargo.toml só atualiza [package].version; [dependencies] ficam intactas.
  • Short-circuit anti-erro: se a versão do topo do CHANGELOG já é uma tag git local → emite EXISTS:tag/<tag>:already released — bump CHANGELOG to release a new version e sai com exit 0 antes de tocar em manifests, commit ou push. Pega o erro mais comum: rodar release depois de commit docs-only sem ter atualizado o CHANGELOG. A correção é adicionar uma nova entrada no topo e rodar de novo.
  • Notes vêm do CHANGELOG: o conteúdo entre o header da versão e o próximo ## [ é usado como release notes.

Warning

release faz git add -A + commit antes do tag/push. Garanta que o working tree esteja limpo do que você não quer comitar — em particular arquivos não relacionados ao bump. Use git status antes.


Dry-run support

Todos os comandos com efeito remoto ou destrutivo aceitam --dry-run. O flag faz o script imprimir as linhas que seriam emitidas (DRY_RUN:<type>/<name>) sem executar nenhuma chamada gh ou alteração local.

Command --dry-run? Comentário
install preview do symlink
create-repo preview da criação remota
setup-labels lista labels que seriam criadas
setup-board preview do project board + colunas + fields
create-milestones preview por milestone
create-issues preview por issue (resolve body_file na hora)
apply-labels preview do delta (+a,-b) por issue
release preview de bump+commit+push (+ tag+release com --publish)
find-project já é read-only; flag preserva contrato
teardown-repo lista o que seria deletado
nuke-project preview da destruição completa (recomendado antes do real)
bootstrap-issues apenas escreve JSON local; sem efeito remoto
bootstrap-milestones mesmo motivo

Padrão para validar antes de aplicar:

gh-toolkit nuke-project lipex360/myproj --dry-run        # verificar o que seria destruído
gh-toolkit nuke-project lipex360/myproj --no-confirm     # então executar real

./install.sh --dry-run                                    # preview do symlink
./install.sh                                              # então criar

Tip

Em nuke-project, o --dry-run é especialmente importante porque o comando deleta repo + branches + arquivos locais em sequência. Sempre rode dry-run primeiro.


Reset for re-testing

# Wipe issues/milestones/labels (keeps repo + project board link)
gh-toolkit teardown-repo lipex360/myproj

# Wipe everything including the project board itself
gh-toolkit teardown-repo lipex360/myproj --delete-project

Nuke (full destruction)

Warning

nuke-project is destructive and irreversible. It deletes the GitHub repository, both branches, and local fixture/checklist files. Always start with --dry-run.

Use only when abandoning a project. Requires gum (brew install gum).

# Preview what would be destroyed (no changes)
gh-toolkit nuke-project lipex360/myproj --dry-run

# Destroy with interactive confirmation
gh-toolkit nuke-project lipex360/myproj

# Destroy without confirmation (CI / scripted)
gh-toolkit nuke-project lipex360/myproj --no-confirm

# Destroy remote only — keep local branch and dev/ files
gh-toolkit nuke-project lipex360/myproj --keep-local

# Slug differs from repo name
gh-toolkit nuke-project lipex360/myproj --slug myproject-mvp

Destroys in this order:

  1. Issues, milestones, labels, project board (via teardown-repo --delete-project)
  2. GitHub repository (gh repo delete)
  3. Remote branch feat/<slug>
  4. Local branch feat/<slug> (in current cwd)
  5. dev/checklist-<slug>.md and dev/fixtures/<slug>/ (in PROJECT_ROOT)

Output contract

All scripts emit machine-parseable lines on stdout:

Line Meaning
CREATED:<type>/<name> resource created
EXISTS:<type>/<name> already existed (idempotent)
DELETED:<type>/<name> resource destroyed
SKIPPED:<type>/<name>:<reason> not found, nothing to do
DRY_RUN:<type>/<name> preview, no change
FAILED:<type>/<name>:<msg> error
GH_NOT_AUTH run gh auth login
REPO_NOT_FOUND:<repo> repo missing — earlier step failed
GH_RATE_LIMITED:remaining=N:threshold=N:resets_at=HH:MM:SS rate limit too low

Logs (stderr + file) go to <project-root>/.logs/scripts/<name>.log.


Testing

Suite de testes em bats-core. 265 tests covering all scripts and install.sh.

# Run full suite
bats tests/

# Run single file
bats tests/create-repo.bats

# Parallel (requires GNU parallel)
bats --jobs 4 tests/

Install bats via Homebrew:

brew install bats-core

Tip

Tests use mocked gh binaries via PATH injection — no GitHub API calls happen during the suite. Integration tests against real gh are gated behind lipex360x/nonexistent-repo-* to verify error paths only.


Linting

Static analysis via shellcheck. 0 issues across scripts, lib, bin, and tests.

shellcheck -x install.sh scripts/*.sh lib/*.sh bin/gh-toolkit tests/*.bats

Install via Homebrew:

brew install shellcheck

The -x flag follows # shellcheck source=... directives so sourced files are analyzed in context.


Prerequisites

Tool Used by Install
gh (authenticated) all scripts that touch GitHub brew install gh && gh auth login
jq bootstrap-, create-, gh-preflight brew install jq
gum nuke-project (interactive confirm) brew install gum
bash 4+ all macOS default 3.2 works for these scripts
bats-core (dev) running the test suite brew install bats-core
shellcheck (dev) linting brew install shellcheck

Standard labels (14)

Applied by setup-labels:

  • Priority: P0, P1, P2
  • Size: XS, S, M, L, XL
  • Type: feat, fix, chore, refactor, docs, test

Project board structure (setup-board)

  • Board name: Board
  • Columns (8): Backlog, Ready, In Progress, In Review, Blocked, Done, Won't Do, Cancelled
  • Custom fields (2): see setup-board.sh for current schema

Source

Originally part of pi-cli. This is a copy adapted to be invoked from any working directory. Bug fixes should be ported back upstream.

About

Standalone GitHub project setup/teardown scripts (extracted from pi-cli, runs from any cwd)

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages