A Go CLI that analyzes a GitHub account via the GitHub REST API and produces a JSON evidence document describing the authenticated user's engineering activity. It is useful for building an engineering CV, portfolio, or profile: for every repository the user has contributed to, it records commit activity, programming languages, permissions, and links back to the actual commits.
- Authenticates against GitHub and scans every repository the token can access (owned, collaborated, and organization membership).
- Per repository, captures:
- Metadata: URL, visibility, description, default branch, fork/archived flags
- Your permissions (admin, maintain, push, triage, pull)
- Activity: total commit count, first and last contribution dates
- Language breakdown with byte counts and percentages
- The 20 most recent commits (SHA, message, author, date, URL)
- A human-readable
safe_summaryline suitable for a CV
- Aggregate totals across all scanned repositories.
- Concurrent scanning (worker pool) for speed.
- Skips forked repositories by default; optionally includes them.
- Optional
-sincefilter to count only commits after a given date.
- Go 1.25 or newer (
go.moddeclaresgo 1.25.0) - A GitHub personal access token (classic or fine-grained) with read access to the
repositories you want to scan. A classic token with the
reposcope works for private repositories; a public-only token covers public contributions.
go build -o github-profiler .The CLI requires a token in GITHUB_TOKEN. Keep it private: do not commit it,
add it to a source file, or share it in screenshots.
Install GitHub CLI, then authenticate in your browser:
gh auth loginChoose GitHub.com, HTTPS, then Login with a web browser. Confirm the authorization, then use the token GitHub CLI stores securely for the current shell:
export GITHUB_TOKEN="$(gh auth token)"
./github-profilerVerify the account at any time with:
gh auth statusOpen GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Give it a descriptive name, short expiration, and access to the repositories you want to scan. Grant repository Contents: Read-only, generate the token, copy it immediately, then run:
export GITHUB_TOKEN='github_pat_...'
./github-profilerFor private repositories across multiple organizations, a fine-grained token may not
cover every resource owner. If your organization allows it, create a classic token
with the repo scope instead. See GitHub's official
personal access token guide
for current permission and organization-approval requirements.
The token is read from the GITHUB_TOKEN environment variable; it is required.
export GITHUB_TOKEN=ghp_xxx
./github-profilerThe tool logs its progress to stderr and writes the evidence document to a
timestamped filename by default, such as evidence-20260806T120000.123456789Z.json.
Set -file-name-with-timestamp=false to write to the exact -output path.
| Flag | Default | Description | Token permission when enabled |
|---|---|---|---|
-output |
evidence.json |
Output JSON file path. | — |
-file-name-with-timestamp |
true |
Append a UTC timestamp to the output filename. | — |
-with-toon |
false |
Write TOON output with a .toon extension. |
— |
-since |
(none) | Only count commits since this date. | — |
-concurrency |
4 |
Number of repositories scanned concurrently. | — |
-max-recent-commits |
20 |
Maximum recent commits recorded per repository. | — |
-max-repositories |
0 |
Maximum contributed repositories in final output. | — |
-max-repository-scans |
0 |
Maximum eligible repositories to scan. | — |
-include-forks |
false |
Include forked repositories in the scan. | — |
-exclude-private |
false |
Exclude private repositories from the scan. | — |
-include-pull-requests |
false |
Include pull requests authored by you. | Classic: repo; fine-grained: Pull requests: Read-only |
-include-reviews |
false |
Include pull request reviews submitted by you. | Classic: repo; fine-grained: Pull requests: Read-only |
-include-issues |
false |
Include issues authored by you. | Classic: repo; fine-grained: Issues: Read-only |
-include-discussions |
false |
Include discussions created by you. | Classic: repo; fine-grained: Discussions: Read-only |
-exclude-repo |
(none) | Exclude an owner/name repository; may be repeated. |
— |
-exclude-org |
(none) | Exclude repositories owned by an organization; may be repeated. | — |
-sort-repo-by |
last-contribution-date-desc |
Repository sort criterion; may be repeated. | — |
For example, to skip a private repository and a fork:
./github-profiler \
-max-repository-scans 50 \
-max-repositories 20 \
-max-recent-commits 10 \
-exclude-repo your-account/private-project \
-exclude-repo upstream/example-fork \
-exclude-org example-organizationExclusions are applied before -max-repository-scans, so it caps the number of
repositories actually scanned. Repositories are considered in GitHub's full-name
sort order. -max-repositories is applied afterwards: it limits the final sorted
contributed-repository results and recalculates their totals.
Commits are always included. The following flags add opt-in evidence and allow a repository to appear when it contains one of these contributions but no attributed commit:
./github-profiler \
-include-pull-requests \
-include-reviews \
-include-issues \
-include-discussionsFor a fine-grained token, grant read access to Pull requests, Issues, and Discussions for the selected repositories. Reviews can generate substantially more API requests because GitHub exposes them per pull request.
A classic token with repo covers all private-repository features above. For public
repositories, GitHub permits many endpoints without those private-repository scopes.
Use -sort-repo-by once or multiple times. Criteria are evaluated in the order
provided: the first is the primary sort and each later criterion breaks ties.
./github-profiler \
-sort-repo-by=star-count-desc \
-sort-repo-by=project-size-descAvailable criteria:
creation-date-desc,creation-date-ascfirst-contribution-date-desc,first-contribution-date-asclast-contribution-date-desc,last-contribution-date-ascstar-count-desc,star-count-ascfork-count-desc,fork-count-asccommit-count-desc,commit-count-ascproject-size-desc,project-size-asc
Without this flag, repositories remain sorted by latest contribution first.
project-size uses the repository size reported by GitHub, in KB. Each repository
also includes its star_count, fork_count, and project_size_kb in the output JSON.
| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_TOKEN |
yes | — | Personal access token used for API calls. |
GITHUB_API_URL |
no | https://api.github.com |
Override for GitHub Enterprise API base URL. |
The output is a pretty-printed JSON document with the following top-level structure:
{
"profile": {
"github": "octocat",
"name": "The Octocat",
"email": "octocat@example.com",
"generated_at": "2026-08-06T12:00:00Z"
},
"totals": {
"accessible_repositories": 12,
"contributed_repositories": 8,
"public_repositories": 5,
"private_repositories": 3,
"commits": 342
},
"repositories": [
{
"repository": "octocat/hello-world",
"owner": "octocat",
"name": "hello-world",
"url": "https://github.com/octocat/hello-world",
"visibility": "public",
"description": "My first repository",
"default_branch": "main",
"fork": false,
"archived": false,
"star_count": 80,
"fork_count": 9,
"project_size_kb": 108,
"permissions": {
"admin": true,
"maintain": true,
"push": true,
"triage": true,
"pull": true
},
"activity": {
"commit_count": 42,
"first_contribution": "2023-01-15T10:00:00Z",
"last_contribution": "2026-07-30T09:30:00Z"
},
"languages": [
{ "name": "Go", "bytes": 20480, "percentage": 80.0 },
{ "name": "Markdown", "bytes": 5120, "percentage": 20.0 }
],
"recent_commits": [
{
"sha": "abc123",
"message": "Fix flaky test",
"author": "octocat",
"email": "octocat@example.com",
"date": "2026-07-30T09:30:00Z",
"url": "https://github.com/octocat/hello-world/commit/abc123"
}
],
"safe_summary": "Contributed to My first repository, using primarily Go, Markdown."
}
],
"warnings": [
"Only repositories accessible to the supplied token were scanned.",
"Commit attribution depends on GitHub associating commits with the authenticated username.",
"Squashed commits, alternate unverified emails, reviews, issues, discussions, and pair-programming activity may be missing.",
"Private repository names and commit messages must be reviewed before publishing."
]
}Details worth knowing:
- Repositories with no commits attributed to the authenticated user are omitted.
- Repositories are sorted by most recent contribution (descending).
- Languages are sorted by byte count (descending), with percentages rounded to two decimals.
- Only the first line of each commit message is included in
recent_commits. - The output file is written with
0600permissions to keep private data safe.
GET /user— identifies the authenticated user.GET /user/repos(paginated, 100 per page) — lists all accessible repositories, coveringowner,collaborator, andorganization_memberaffiliations.- For each repository, concurrently:
GET /repos/{owner}/{repo}/commits?author={username}(paginated, optionalsince)GET /repos/{owner}/{repo}/languages
- Results are aggregated into the evidence document and written to the output file.
The project stays as a single main package because it is a small, self-contained CLI.
Files are separated by responsibility so the application flow and GitHub integration are
easy to navigate:
.
├── main.go # CLI orchestration: fetch, analyze, sort, and write
├── config.go # Flags, environment variables, and validation
├── models.go # GitHub API and evidence-document data structures
├── github_client.go # GitHub REST requests, pagination, and API errors
├── analyzer.go # Concurrent repository analysis and commit evidence
├── evidence.go # Evidence-document creation and aggregate totals
├── output.go # JSON serialization and secure file writing
└── helpers.go # Date, repository-name, summary, and rounding helpers
If the CLI gains more commands or integrations, the next step would be to move these
concerns into internal/ packages (for example internal/github and
internal/evidence) while keeping main.go as the command entry point.
- The scan only sees repositories the supplied token can access. Totals reflect what the token can see, not necessarily the entire account.
- Commit counts depend on GitHub's author association. Squashed commits, commits made under unverified or alternate emails, reviews, issues, discussions, and pair-programming work are not counted.
- The output contains private repository names and commit messages — review the JSON before sharing or publishing it.