GitHub CLI (gh) — Structure#

Layout pattern#

Modified Standard Go Layout (cmd/internal/pkg)

gh follows the standard Go community layout with cmd/, internal/, and pkg/ at the root, but makes an unconventional choice: the bulk of command implementations live in pkg/cmd/ (public) rather than internal/cmd/. This is by design — the extension author SDK (go-gh) and doc-generation tooling need to reference types from those packages. The actual main entry point (cmd/gh/main.go) is a 7-line stub that delegates everything to internal/ghcmd.

Directory map#

gh/
├── acceptance/          # Acceptance/E2E test suite (build-tagged, runs against live GitHub)
│   └── testdata/        # Fixtures for acceptance tests
├── api/                 # GitHub REST + GraphQL API client (queries, HTTP client, model types)
├── build/               # Platform-specific build assets
│   ├── macOS/           # macOS pkg/notarization resources
│   └── windows/         # Windows installer resources
├── cmd/                 # Binary entry points only
│   ├── gen-docs/        # CLI tool to generate man pages and website docs
│   └── gh/              # Main gh binary — 7-line wrapper calling internal/ghcmd
├── context/             # Remote resolver: maps git remotes to GitHub repos
├── docs/                # Doc assets (primer design guidelines)
├── git/                 # Git subprocess wrapper (git client, URL handling, objects)
├── internal/            # Private implementation packages
│   ├── agents/          # GitHub Copilot agent task detection
│   ├── authflow/        # OAuth device + web flow implementation
│   ├── browser/         # Browser-open abstraction (interface + stub)
│   ├── build/           # Version/date injected via ldflags at build time
│   ├── codespaces/      # Codespaces API client, portforwarder, SSH tunnel
│   ├── config/          # User config file read/write (hosts, settings)
│   ├── docs/            # Cobra → man page and markdown doc generators
│   ├── featuredetection/# GHES version-gated feature flag detection
│   ├── gh/              # Core interfaces: Config, AuthConfig, and key option types
│   ├── ghcmd/           # Real main() logic: cobra root setup, update check dispatch
│   ├── ghinstance/      # GitHub instance hostname normalization (ghes, ghe, github.com)
│   ├── ghrepo/          # GitHub repo reference: owner/name parsing, interface
│   ├── keyring/         # System keyring abstraction for credential storage
│   ├── licenses/        # Embedded SPDX license data
│   ├── prompter/        # Terminal prompt abstraction (huh / accessible fallback)
│   ├── run/             # subprocess exec wrapper + stub for testing
│   ├── safepaths/       # Path sanitization utilities
│   ├── tableprinter/    # Terminal table rendering
│   ├── text/            # Text formatting helpers (truncate, pluralize, etc.)
│   ├── update/          # Background version-update checker
│   └── zip/             # ZIP extraction utility (for extension installs)
├── pkg/                 # Public packages (usable by extension authors and tests)
│   ├── cmd/             # All 35+ command implementations (one subdir per command)
│   │   ├── accessibility/
│   │   ├── actions/
│   │   ├── agent-task/
│   │   ├── alias/
│   │   ├── api/
│   │   ├── attestation/
│   │   ├── auth/
│   │   ├── browse/
│   │   ├── cache/
│   │   ├── codespace/
│   │   ├── completion/
│   │   ├── config/
│   │   ├── copilot/
│   │   ├── extension/
│   │   ├── factory/     # Factory pattern: constructs shared dependencies (IOStreams, API client, Config)
│   │   ├── gist/
│   │   ├── gpg-key/
│   │   ├── issue/
│   │   ├── label/
│   │   ├── licenses/
│   │   ├── org/
│   │   ├── pr/
│   │   ├── preview/
│   │   ├── project/
│   │   ├── release/
│   │   ├── repo/
│   │   ├── root/        # Root cobra command, wires all subcommands together
│   │   ├── ruleset/
│   │   ├── run/
│   │   ├── search/
│   │   ├── secret/
│   │   ├── ssh-key/
│   │   ├── status/
│   │   ├── variable/
│   │   ├── version/
│   │   └── workflow/
│   ├── cmdutil/         # Shared command infrastructure: Factory struct, errors, flags
│   ├── extensions/      # Extension manager interface and types
│   ├── findsh/          # Locate sh interpreter on Windows
│   ├── githubtemplate/  # Issue/PR template discovery
│   ├── httpmock/        # HTTP mock registry for tests (public for extension test use)
│   ├── iostreams/       # IO abstraction (color, pager, tty detection, test helpers)
│   ├── jsoncolor/       # JSON colorizer for terminal output
│   ├── jsonfieldstest/  # Test helper for JSON field coverage assertions
│   ├── markdown/        # Markdown → terminal renderer (wraps glamour)
│   ├── option/          # Generic Option[T] type
│   ├── search/          # GitHub search query builder and response types
│   ├── set/             # Generic set type
│   ├── ssh/             # SSH config reader
│   └── surveyext/       # survey v2 custom widgets (editor launch, etc.)
├── script/              # Build automation
│   ├── build.go         # Cross-platform build script (Go program, not shell)
│   └── release          # Release automation script
├── test/
│   └── integration/     # Integration test helpers
└── utils/               # Legacy utility shims (minimal, kept for compatibility)

Entry points#

BinaryPathPurpose
ghcmd/gh/main.gointernal/ghcmd.Main()Primary GitHub CLI — all user-facing commands
gen-docscmd/gen-docs/main.goGenerates man pages (--man-page) and website markdown (--website) from the Cobra command tree

cmd/gh/main.go is deliberately minimal (7 lines). All cobra setup, update checks, and command registration happen in internal/ghcmd.

Package organization#

  • Internal packages:

    • internal/gh — core Config and AuthConfig interfaces; the canonical contract between config and command layers
    • internal/ghcmd — true application bootstrap: cobra root construction, update check, exit code dispatch
    • internal/ghrepoInterface for owner/name repo references, used everywhere
    • internal/config — reads/writes ~/.config/gh/ config files
    • internal/authflow — OAuth device/web flow; credentials injected via build ldflags
    • internal/codespaces — Codespace API + SSH port-forwarder (largest internal package)
    • internal/featuredetection — GHES version feature gates
    • internal/prompter — interactive prompt abstraction (huh TUI + accessible text fallback)
    • internal/tableprinter — consistent table rendering across commands
    • All others are focused single-responsibility utilities (browser, run, text, update, zip, etc.)
  • Public packages (pkg/):

    • pkg/cmd/factory — constructs the shared cmdutil.Factory (HTTP client, IOStreams, Config, ExtensionManager) used by every command
    • pkg/cmdutilFactory struct and error types that commands receive as dependencies
    • pkg/iostreams — I/O layer with color detection, pager support, and test helpers
    • pkg/extensionsExtensionManager interface + Extension type
    • pkg/httpmock — test HTTP registry (public so extension authors can use it)
    • pkg/search, pkg/set, pkg/option — domain utility types
  • Layering: The architecture follows a clean dependency direction:

    cmd/gh → internal/ghcmd → pkg/cmd/root → pkg/cmd/<feature> → api/ + internal/*

    Commands depend on cmdutil.Factory (received via DI), never constructing API clients directly. internal/ packages do not import from pkg/cmd/. The api/ package sits outside internal/ as a first-class layer.

Build system#

  • Build tool: Make + custom script/build.go Go program
  • Key targets:
    • make bin/gh → compiles main binary via script/build.go (injects version, date, OAuth secrets via ldflags)
    • make manpages → runs gen-docs --man-page → writes to share/man/man1/
    • make completions → generates shell completions for bash/fish/zsh
    • make testgo test ./...
    • make acceptance → acceptance suite with -tags acceptance
    • make site-docs → clones cli.github.com and generates website markdown
  • Docker: No — distributed as compiled binaries; platform packaging handled by build/macOS/ (pkg) and build/windows/ (installer) directories plus script/pkgmacos, script/distributions for Linux

Notable structural decisions#

  1. Commands live in pkg/cmd/, not internal/cmd/: This is deliberate — the companion library go-gh and extension authors need to reference command-level types. The tradeoff is that all 35+ command packages are technically importable externally, though the team relies on convention rather than access control.

  2. internal/ghcmd as the real main: The cmd/gh/main.go is a 7-line shell. This separation means the entire startup sequence (cobra wiring, update checking, exit code handling) is testable as a package, not locked inside main.

  3. Cross-platform build system written in Go: script/build.go is a Go program invoked via go run, avoiding Makefile portability issues on Windows. The Makefile is a thin wrapper that compiles script/build.go first.

  4. api/ at root, not inside internal/: The GitHub API client layer is prominent and top-level, signaling that it’s a first-class, long-lived concern (not an implementation detail). It contains both REST query functions and GraphQL model types.

  5. Testing infrastructure in pkg/: pkg/httpmock and pkg/iostreams (with its Test() constructor) are public packages so that extension authors and downstream consumers can write tests using the same mocking primitives as the gh team itself.

  6. Dual acceptance vs integration test scopes: acceptance/ (build-tagged, requires GitHub credentials, tests real API behavior) is separate from test/integration/ (in-process integration helpers). This keeps the slow/credential-dependent tests isolated from the faster integration layer.