| name | gh-org-chart |
| license | MIT |
| description | Generate an interactive HTML org explorer from a GitHub organization. Surfaces team hierarchy, members, owned repos, and CODEOWNERS path attributions. Two stages: collect.sh writes a canonical JSON, render.py emits a self-contained HTML file. The JSON is the cache (mtime ≤ 24h skips re-collection) and is hand-editable. Triggers: "org chart", "team chart", "visualize github org", "who owns this repo", "github team hierarchy", "/gh-org-chart"
|
| argument-hint | <org> [--refresh] [--no-codeowners] [--no-members] |
| compatibility | Requires gh CLI authenticated with read:org scope, jq, and python3 |
| metadata | {"author":"jackchuka","scope":"generic","layer":"workflow","confirms":["save to filesystem"]} |
GitHub Org Chart
Produces two artifacts in ${TMPDIR:-/tmp}/gh-org-chart/:
<org>-org.json — canonical data: teams, members (with role), repos (with archived flag and permission), CODEOWNERS paths. Cache + hand-editable source of truth.
<org>-org.html — single self-contained interactive explorer rendered from the JSON. Opens via file://, works offline.
Set OUT_DIR once at the start of every phase and reuse it:
OUT_DIR="${TMPDIR:-/tmp}/gh-org-chart"
mkdir -p "$OUT_DIR"
Arguments
/gh-org-chart — prompt for org.
/gh-org-chart <org> — render-if-fresh: if <org>-org.json exists and mtime is within 24h, re-render from it. Otherwise collect, then render.
/gh-org-chart <org> --refresh — force re-collect.
/gh-org-chart <org> --no-codeowners — skip CODEOWNERS scan (faster on big orgs).
/gh-org-chart <org> --no-members — drop members from collection and output.
Phase 1: Intake
-
Resolve org: if provided as argument, use it. Otherwise ask:
Which GitHub org should I chart?
-
Verify auth: run gh auth status. Confirm read:org is in the scopes line. If not, instruct:
gh auth refresh -s read:org
then re-run the skill.
Phase 2: Decide collect vs. reuse
-
Locate $OUT_DIR/<org>-org.json.
-
If --refresh was passed, or the file does not exist, or its mtime is older than 24h, run collect (Phase 3). Otherwise skip to Phase 4.
Freshness check:
JSON="$OUT_DIR/$ORG-org.json"
if [[ "$REFRESH" == "1" ]] || [[ ! -f "$JSON" ]] || \
[[ $(($(date +%s) - $(stat -f %m "$JSON" 2>/dev/null || stat -c %Y "$JSON"))) -gt 86400 ]]; then
NEEDS_COLLECT=1
fi
Phase 3: Collect
-
Run the bundled collect script (handles zsh noclobber via rm -f):
rm -f "$OUT_DIR/$ORG-org.json"
"${CLAUDE_SKILL_DIR:-$HOME/.claude/skills/gh-org-chart}/scripts/collect.sh" \
"$ORG" $FLAGS > "$OUT_DIR/$ORG-org.json"
Where $FLAGS is built from --no-codeowners and --no-members if set. CODEOWNERS scanning is the long pole — expect ~1 API call per owned repo. Big orgs (200+ owned repos with rate limiting): consider --no-codeowners.
Phase 4: Render
-
Run the renderer:
python3 "${CLAUDE_SKILL_DIR:-$HOME/.claude/skills/gh-org-chart}/scripts/render.py" \
"$OUT_DIR/$ORG-org.json"
This writes <org>-org.html next to the JSON (i.e. in $OUT_DIR).
-
Open it (macOS):
open "$OUT_DIR/$ORG-org.html"
Other platforms: report the path so the user can open it themselves.
Phase 5: Report
- Summarize (set
JSON="$OUT_DIR/$ORG-org.json"):
- Teams:
jq '.teams | length' "$JSON"
- Owned repos (admin or maintain):
jq '[.teams[].repos[] | select(.permission == "admin" or .permission == "maintain") | .name] | unique | length' "$JSON"
- CODEOWNERS path attributions:
jq '[.teams[].repos[].codeowner_paths // [] | length] | add // 0' "$JSON"
- Member entries:
jq '[.teams[].members[]] | length' "$JSON" (omit if --no-members).
- Whether the JSON was freshly collected or reused from cache.
Notes
- Hand-editing the JSON: edits survive across
/gh-org-chart <org> runs because the freshness check reuses the file. Use --refresh when you want collection to overwrite your edits.
- CODEOWNERS attribution: only
@<org>/<team-slug> owners produce attributions. Individual user owners (@alice) and external orgs (@other-org/team-x) are intentionally ignored — this is a team-ownership view.
- No reporting lines: GitHub teams reflect permission grouping, not management hierarchy. Manager → report relationships need a different data source (HRIS).
- Performance: CODEOWNERS scan is restricted to owned repos (permission ≥ maintain) to keep API calls bounded. Collecting roles adds one
?role=maintainer call per team.
- Data shape (for hand-editing / jq):
members is [{login, role}] where role is "maintainer" or "member"; each repo carries permission and an archived flag. --no-members leaves members empty.