Skip to main content

ghbot

Validate Security Maintenance PyPI version Python versions License: MIT

An async command-line tool that scans GitHub repositories across one or more owners and reports on them. ghbot is a small engine — repository discovery, bounded-concurrency execution, a GitHub API layer, and report rendering — plus a set of processors that each answer one question about a repository (security posture, open issues, …). New capabilities are added by writing a processor.

Requirements

  • The GitHub CLI (gh), installed and authenticated (gh auth login).

Authentication

ghbot has no credentials of its own. At startup it runs gh auth token and uses the token that gh already holds from your gh auth login — it does not generate a new token. Your effective access is therefore exactly your gh login's scopes.

That token is fetched once, cached in memory for the duration of the run, and never written to disk (if gh is missing or you're not logged in, ghbot stops with a clear message). gh is used only to obtain the token — all repository data is then fetched by calling the GitHub REST API directly (via httpx + gidgethub), not by shelling out to gh for each request.

Running

uvx ghbot --owner <owner> <command> [options]

--owner may be repeated or comma-separated, and targets a GitHub user (all of that user's repositories are scanned):

uvx ghbot --owner acme security
uvx ghbot --owner acme,globex issues --json --labels

(From a source checkout, use uv run ghbot … instead of uvx ghbot ….)

Commands (built-in processors)

security — open security findings

Scans each repository for open Dependabot, code-scanning, and secret-scanning alerts, and open Dependabot pull requests (and how many are ready to merge once approvals are ignored).

uvx ghbot --owner acme security [--plain | --table | --json] [--output FILE]
Option Meaning
--plain (default) Summary + a per-repo block for repos with findings
--table Markdown table, one row per repo with findings
--json Structured output for every scanned repo
--output FILE Write the report to a file instead of stdout

Counts that are unavailable (no access / feature off) show as N/A in text and null in JSON; 0 means accessible with nothing open.

issues — open issues and pull requests

Lists open issues and pull requests per repository, each tagged by type (issue or pr).

uvx ghbot --owner acme issues [--plain | --table | --json] [--labels] [--output FILE]
Option Meaning
--plain (default) Per-repo header + #<n> [<type>] <title> lines
--table One row per item: Repository │ # │ Type │ Description
--json repositories[] with open_item_count and an items[] array
--labels Include GitHub labels (+label in plain, a Labels column in table, a labels[] array in JSON)
--output FILE Write the report to a file

The summary splits Open issues and Open PRs. Plain/table omit repos with nothing open; JSON includes every scanned repo.

info — repository summary statistics

Gathers per-repository facts (description, languages, contributors, latest release, file count, open PRs/issues, security alerts). These are collected and logged during the scan; info does not currently render a stdout report.

Global options

Global options go before the command:

Option Meaning
--owner <o> Owner(s) to scan; repeatable or comma-separated
--concurrency <n> Max repositories processed at once (default 16)
--config <path> Config file (see below)
--verbose / --quiet Console log level: debug / warnings-only
--log-file <path> Also write logs to a file
--log-level <lvl> File log level: trace/debug/info/warn/error
--log-json Structured JSON logs to the log file
--requests Log HTTP request/response detail

Configuration

Options can be set in ghbot.toml (current directory) or ~/.config/ghbot/config.toml. Some CLI flags override the file (notably --owner, --concurrency, --log-file, and --log-json).

owners = ["acme", "globex"]
concurrency = 16
log-level = "info"
log-json = false

How it works

gh auth token
      │
scan_repositories(owners)          discover every repo for each owner
      │  (owner, repo) stream
TaskPool (bounded concurrency)     one processor instance per repo, run in parallel
      │
Processor.run() ─► RepoResult      fetch via the GitHub API, return structured results
      │
report renderer                    plain / table / JSON
  • GitHub API access goes through a single wrapper that maps HTTP failures to typed errors (not-found, rate-limit, transient) so processors handle them uniformly.
  • Resilience: a failed step yields a partial result rather than aborting the run; rate limits abort the pool cleanly.
  • The extension point is the processor — everything else is stable engine.

Extending ghbot: adding a processor

A processor is any object implementing the RepoProcessor protocol (src/ghbot/processor.py):

from typing import Protocol, runtime_checkable

@runtime_checkable
class RepoProcessor(Protocol):
    @property
    def owner(self) -> str: ...
    @property
    def repo(self) -> str: ...
    async def run(self) -> RepoResult: ...

It returns a RepoResult:

@dataclass(slots=True)
class RepoResult:
    owner: str
    repo: str
    status: ProcessingStatus = "success"      # "success" | "partial" | "failed"
    errors: list[str] = field(default_factory=list)
    results: dict = field(default_factory=dict)   # your data goes here

1. Write the processor

Constructors take (owner, repo, gh, monitor, options=None). Fetch through github_api_call and build URLs with url_vars (never f-strings). Follow the standard per-step error policy: treat 403/404 as "unavailable" (store None, don't error), turn a non-fatal GitHubApiError into a partial result, and re-raise rate-limit errors so the pool can handle them.

from ghbot.processor import RepoResult, FeedbackEvent
from ghbot.github.client import github_api_call
from ghbot.errors import (
    GitHubApiError, GitHubNotFoundError,
    GitHubPrimaryRateLimitError, GitHubSecondaryRateLimitError,
)

class TagsRepoProcessor:
    """Counts a repository's tags."""

    def __init__(self, owner, repo, gh, monitor, options=None):
        if not owner or not repo:
            raise ValueError("owner and repo are required")
        self._owner, self._repo, self._gh, self._monitor = owner, repo, gh, monitor

    @property
    def owner(self) -> str: return self._owner
    @property
    def repo(self) -> str: return self._repo

    async def run(self) -> RepoResult:
        result = RepoResult(owner=self._owner, repo=self._repo)
        try:
            async with github_api_call(self._gh):
                tags = [
                    t async for t in self._gh.getiter(
                        "/repos/{owner}/{repo}/tags",
                        url_vars={"owner": self._owner, "repo": self._repo},
                    )
                ]
            result.results["tag_count"] = len(tags)
        except GitHubNotFoundError:
            result.results["tag_count"] = None
        except (GitHubPrimaryRateLimitError, GitHubSecondaryRateLimitError):
            raise
        except GitHubApiError as e:
            result.results["tag_count"] = None
            result.errors.append(f"tags: {e}")
            result.status = "partial"
        return result

Use the injected monitor (await self._monitor.send_event(FeedbackEvent(type, severity, message))) for progress/feedback. InfoRepoProcessor and SecurityRepoProcessor are worked examples.

2. Wire a subcommand

Add a Click command in src/ghbot/__main__.py that runs your processor across all discovered repos via the shared engine, then (optionally) renders a report:

@cli.command()
@click.pass_context
def tags(ctx):
    """Count repository tags."""
    cfg = ctx.obj["cfg"]
    results = asyncio.run(_main(cfg, TagsRepoProcessor))
    # render results as you like, e.g. a function in report.py

_main(cfg, processor_cls) handles auth, discovery, concurrency, and per-repo construction — you only supply the processor (and, if it needs one, a renderer in src/ghbot/report.py).

3. Test it

Mirror tests/unit/test_*_processor.py: patch ghbot.processor.github_api_call, drive run() with a mocked gh, and assert on result.results / result.status / result.errors.

Development

uv sync
uv run pytest -q          # tests
uv run ruff check         # lint
uv run ruff format        # format
uv run ty check src/      # type check

Contributions go through pull requests; main is protected and gated by the Validate workflow.

License

MIT — see LICENSE.md.

Metadata

Release files for ghbot 0.9.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ghbot 0.9.5
File Size Uploaded
ghbot-0.9.5.tar.gz 17.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ghbot 0.9.5
File Interpreter ABI Platform
ghbot-0.9.5-py3-none-any.whl Python 3 none any Details

Total release size: 38.3 kB

Release files / ghbot-0.9.5.tar.gz

Download URL ghbot-0.9.5.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
b69f87ce5983cb83cc94803ad3760de4abc66e191fdea66ca8a1f1917177cf01
BLAKE2b-256 checksum
How to use checksums
dc386f28c738d80f97c265977ba30c02ca056678bebefce1f80cf3fc7cda864d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / ghbot-0.9.5-py3-none-any.whl

Download URL ghbot-0.9.5-py3-none-any.whl
Size 21.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30284e349fd212134e917176a4db8c99c6408a41bbaf6e43783014194f9d2182
BLAKE2b-256 checksum
How to use checksums
6d05c8f741fe134a7faa27cf3486db8d293a0167bb2f42d46ea19ba2c084e935
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.9.5 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page