Made with glancecard · refreshed daily
OCTOSCOPE is a terminal dashboard for your GitHub account, or anyone else's public profile — profile, activity, repo health and network at a glance, auto-refreshed in the background.
Read the guide — install, the tabs, drill-ins, themes, configuration, scripting, and a full keyboard reference.
- What it does
- Install
- Usage
- Scripting —
--plainand--json - Themes
- Configuration
- Authentication
- Contributing
- Community
- Sponsor
- License
octoscope is a single-binary TUI built with BubbleTea. It pulls a focused set of numbers from the GitHub GraphQL API in one round-trip and keeps them current on screen so you can check the pulse of your GitHub life without switching to a browser.
The dashboard is split into tabs (Overview, Repos, PRs, Issues,
Activity, Gists, Inbox, What's new) — jump with number keys or cycle
with tab / shift+tab.
-
Overview — the five stat sections below, the traditional dashboard landing.
-
Repos — every owned, non-fork repository in one sortable, searchable list. Columns: CI status (a coloured dot — green / red / yellow / dim — sourced from the default-branch status-check rollup; drill in with
enterto see which check failed and jump to its run, v0.25.0+), name, primary language, stars, forks, open issues, open PRs, last push, latest release (tag + age, v0.14.0+). Presssto cycle sort (CI is in the cycle and surfaces failures first; "release" sort lists the most recently published first),/to filter by substring,Pon a row to pin a repo to a sticky section at the top — the viewport scrolls so even a 100-repo account stays navigable. Setcommit_counts = trueto add a commits column — how many commits you authored in each repo over the last year, with its own sort (v0.32.0+). Off by default: it is the one column that costs a query of its own on every refresh. Addwatch_repos = ["..."]to the config to monitor repositories you don't own — they appear in a third "Watched" section under your own list. Pressw(v0.18.0+) to cycle the work filters — PRs open, CI broken, stale 90d — quick presets for "where is my attention needed"; they compose with the/search and apply to pinned / owned / watched sections alike.escclears both filters at once (while the search box is open,escfirst cancels that input — press it again to clear the work preset too). -
PRs — every open pull request you've authored, across every repo. Number, title, repo, state (draft / ready / conflicts) and last-update time; a layer of a stacked pull request carries its place in the stack ahead of the title,
2/4(v0.38.0+). Same sort & search idioms as Repos. PRs awaiting your review (v0.15.0+) surface in a sticky section at the top of the tab when someone has requested you as a reviewer — separated from your authored list by a muted rule, ordered most-recently-updated first. -
Issues — every open issue you've authored, wherever it lives. Same shape as PRs minus the state column.
-
Activity — two halves, switched with
←/→. Heatmap is the 52-week contribution calendar, plus total / current streak / longest streak / busiest day computed from the same cells. Feed (v0.29.0+) is the timeline underneath it: what you actually did, most recent first. -
Gists (v0.29.0+) — your gists, newest first, with visibility, file count and stars.
enterdrills in and shows the file contents, syntax-highlighted, andcthere copies the code rather than the link — a gist is a snippet, so reading and pasting it is the point. A one-file gist opens straight into it. An untitled gist is listed by its first filename rather than by its hash, and--public-onlydoesn't fetch the secret ones at all, so not even the count reveals how many there are. -
Inbox (v0.29.0+) — your actual GitHub notification inbox: mentions, review requests, assignments, subscriptions, CI activity, newest first. Unread only — which is what an inbox is, and worth saying because it has a visible consequence: open a thread and it is gone from the tab on the next reload, because GitHub marked it read when you got there.
enteropens the thread on GitHub,ccopies its link,/filters, andscycles a three-way filter — all, involving you (the reasons that name you specifically: mention, team mention, review requested, assigned, author, security alert) and ci.That filter is there because of a measurement, not a preference: of 104 notifications on a real account, 77 were CI activity — three quarters of an inbox reporting that a workflow succeeded. The default is still all, because an inbox that hides most of itself is worse than a noisy one, and the line under the table always says how many rows the current filter is hiding.
Worth knowing:
- It needs a classic token. GitHub documents
/notificationsas supporting "only … a personal access token (classic)", with thenotificationsorreposcope. A fine-grained token — which the Token scopes section otherwise recommends — cannot read it at all, and the tab says so instead of looking broken. - It is read-only, like the rest of octoscope. Marking a thread read
is a
PATCH, and octoscope does not mutate GitHub state — soentertakes you to the thread, where GitHub marks it read. There is deliberately no--allow-mutationsflag: that would make this a different product with a different trust model. - It loads when you open the tab, not on every refresh. GitHub asks
callers to poll
/notificationsno more than once a minute, and--refreshgoes down to 5s. - GitHub does not return the inbox in time order — measured — so
octoscope sorts it. Notifications from private repositories are dropped
under
--public-only. - Scripts can read it too (v0.37.0+):
--inboxadds the same page to the--plain/--jsonreport — see Scripting.
- It needs a classic token. GitHub documents
-
What's new (v0.16.0+) — the highlights of the version you're running, bundled into the binary so it works offline, plus a sponsor section (
oopens the Sponsors page,ccopies the link). Jump here any time with8.
The Overview tab is organised in six sections:
-
Profile — name, login, pronouns, bio, company, location, website, and how many years you've been on GitHub
-
Social — Followers · Following · Stars received (across your non-fork repositories) · plus a 4th Stars + Forks card when you own forks that carry stargazers, so the dashboard reconciles with counters that don't filter forks out · plus Sponsors and Sponsoring cards (v0.29.0+) when there is any sponsorship in that direction
-
Activity — lifetime PRs authored and merged, lifetime issues authored, and commits in the last 12 months, with a takeaway line ("X% of N PRs merged · M still open") summarising the funnel; underneath, a Languages bar (byte counts across your owned repos, colour-matched to GitHub's own hex palette) and a Top repositories column ranking your five most-starred owned non-fork repos
-
Operational — repositories, forks received, open issues (own) and open PRs (own) across your owned repositories — the (own) qualifier disambiguates from your lifetime authored counts above
-
Sponsors (v0.29.0+) — who funds this account and who it funds, by name, with GitHub's own monthly income estimate. The whole section is absent unless there is something to say: an account with no sponsorship and no listing never grows an empty row. An open listing with no sponsors yet says so, because "nobody has sponsored me" and "I can't be sponsored" are different statements.
Two limits are stated rather than papered over. There is no tier, start date or amount, and no way to tell a public sponsor from a private one: every one of those fields needs the
read:userscope, and octoscope is not going to ask you to widen a token so a dashboard can print a tier name. And because the public/private distinction is exactly what can't be read,--public-onlydrops the section entirely — names, counts and income — rather than guessing which sponsors are safe to draw. The income figure is your own or it is not shown at all; GitHub answers that field with 0 for anybody else, so it is never read where a zero would be indistinguishable from "not allowed to know". -
Network — the organisations you're a member of plus your verified social accounts (X, LinkedIn, Bluesky, Mastodon…)
The top header also shows whether the current session is authenticated and
how fresh the data is. Auto-refresh runs 60 seconds after each refresh
lands, yours included; press r at any time for an on-demand refresh.
The banner, profile card, tab bar and footer stay pinned. The body of the
Overview and Activity tabs scrolls vertically when the rendered
content overflows a short terminal — ↑ / ↓ for line, pgup / pgdn
for page, u / d for half a page. A ↑/↓ scroll hint appears in the
footer only when the active tab actually overflows.
On any list tab (Repos, PRs, Issues, Gists), pressing enter on the
selected row opens a rich drill-in view of that item; pressing
space opens the action menu instead:
┌─ Actions for octoscope ─────────────────┐
│ ▸ o Open in GitHub │
│ d View details │
│ c Copy URL │
│ │
│ ↑/↓ select · enter confirm · esc back │
└─────────────────────────────────────────┘
enter confirms the highlighted action; the underlined letter is a
direct shortcut you can press from inside the menu to skip selection.
-
Open in GitHub (
o) — opens the repo / PR / issue in your default browser. Same gesture is bound toodirectly on the list row, so the menu is just one of two paths to the same action. -
View details (
d) — replaces the tab body with a rich drill-in view of the selected item, also reachable directly viaenteron the row (the canonical TUI convention, mirroring lazygit / k9s / ranger). Per tab:- Repos — description, license, languages bar, latest
release, a Checks section (v0.25.0+) breaking the list's CI
dot down into the individual checks on the default branch's tip
— failures first, each name an OSC 8 hyperlink to its run on
github.com,
cto expand past the first 8 on busy repos — Dependabot alerts (v0.38.0+) where GitHub shows them to you (write access or above, unless an organisation keeps them to its admins): the open ones by severity, the five most severe with the package, the advisory linked to the alert, and whether a fixed version exists — a 12-month star-history sparkline (pressv, v0.18.0+, to switch between weekly density and a cumulative growth curve à la star-history.com), traffic (v0.38.0+) — views and clones over the last 14 days, one sparkline each, for the repositories GitHub shows them to you on (push access) — recent commits with total + your-commits-in-the-last-year counts, open issues / PRs preview, topics. - PRs — title + state chip (Open / Draft / Merged / Closed),
a Stack section (v0.38.0+) when the PR is a layer of a
stacked pull request
— up to 20 of its layers in order from the base branch (any
beyond are counted), each with its state, this one marked, so you
see what it sits on and whether that has landed; nothing when the
stack cannot be read —
glamour-rendered description, reviewers, checks summary,
files changed, recent commits, labels, curated timeline.
Press
fto inspect the changed files: a full-screen list with cursor navigation, and Enter on a file opens its unified diff in a syntax-highlighted viewer (chromadifflexer + monokai palette). Both sub-views support↑↓/pgup/pgdn,oto open on github.com,cto copy the path, andescto go back one level. - Issues — title + state chip, glamour-rendered description, assignees, recent comments, labels, linked PRs (those that would close the issue when merged), curated timeline.
Each detail is fetched in a single targeted query for that one item, so it never affects the dashboard's rate-limit budget. The PR detail additionally pulls the file changeset from GitHub's REST
/pulls/{n}/filesin parallel with the GraphQL drill-in — wall-clock latency stays close to the slower of the two. While in the detail view:rrefreshes the data,oopens the item in your browser,ccopies its URL,escgoes back to the list (cursor preserved). - Repos — description, license, languages bar, latest
release, a Checks section (v0.25.0+) breaking the list's CI
dot down into the individual checks on the default branch's tip
— failures first, each name an OSC 8 hyperlink to its run on
github.com,
-
Copy URL (
c) — copies the row's URL to your system clipboard (usespbcopyon macOS,clipon Windows,wl-copy/xclip/xselon Linux). AURL copiedtoast confirms in the footer. -
Security scan (
s, Repos only, v0.20.0+) — runs an on-demand supply-chain integrity scan of the repo and opens a read-only report with a weighted, explainable verdict (clean / watch / suspicious / likely compromised) for the Shai-Hulud / Miasma class of attack — an implant pushed to your repos that auto-runs when you open them in an AI editor or install them. It flags auto-execution surfaces, oversized / obfuscated payloads and forged or unsigned commit tips — a tip wearing the GitHub Actions identity without GitHub's signature scores when such commits recently changed a file on that branch that can run code, when the branch carries an anomalous payload, or when the scan cannot tell (v0.38.0+), since a workflow that pushes withgitproduces exactly that tip — matching the invariant of the attack, not a single filename, so renamed variants still trip it — ranks the evidence behind the verdict heaviest first, lists every auto-executing file it found, and shows per-branch commit-tip provenance. When something looks wrong it offers a copy-paste remediation script (y) and the right OAuth-grant revoke links. octoscope never mutates the repo. To ask the question of every repository at once, see--scan.Each scan also records a fingerprint of the repo's auto-execution surface (v0.27.0+), so the next one reports what changed since last time — a file that auto-executes appeared, an existing one changed, or a branch tip that used to be signed no longer is. That signal survives renames and re-obfuscation, because a variant still has to appear. The first scan of a repo says so explicitly rather than passing for a clean comparison, and a baseline older than 30 days reports without affecting the verdict. The report states how wide its comparison window was (v0.30.0+) — "compared against a baseline recorded 29 days ago" — once, under the verdict. The scan runs when you ask it to, not on a schedule, so a delta measured over four minutes and one measured over four weeks are different claims, and a repository where nothing changed says which it is too. It also keeps an unbounded history (v0.30.0+) of the content it has seen at each path, so a file that changes and later changes back says so — "this exact content was observed here before, on 2026-06-01" — however long the round trip took. A note, never a score: reverting is usually somebody undoing a mistake. It is unbounded because a fixed lookback is a published waiting time, and waiting is the whole point of the attack this is meant to notice. It grows by distinct contents, not by scans — a file that oscillates between two versions forever stores two — so a real repository stays a few kilobytes. This is the one file octoscope writes on its own —
scan-baselines.json, beside your config; deleting it just starts a fresh baseline.When the baseline moves. Every scan that completes attempts the write, automatically and with no confirmation step, whatever the verdict — so a repository scanned while it is already compromised makes that state its own baseline, and a later "nothing changed" means nothing has improved rather than that all is well. The report says exactly that when it fires, but you meet the sentence only after the fact, which is why it is here. A scan that returns an error does not attempt it at all, and the previous baseline survives — but a single unreadable blob or lockfile is not such an error: the report says the file went unread, and the baseline is recorded anyway. The attempt can also fail silently — a store that cannot be written is not reported, and with no usable config directory there is no store at all. And it moves only when you scan: nothing is watching, so the baseline changes when you run the scan from a repository's action menu (
space, thens), not when a commit lands or a lockfile changes. It is per repository, keyedowner/name— repositories with different security postures never interfere, and a rename starts a fresh history rather than inventing a delta.It also compares the dependency install surface (v0.33.0+) — the subset of your npm dependencies that run code at install time, read from
package-lock.json/npm-shrinkwrap.jsonon the default branch. A dependency that did not run code at install and now does is a finding; the same version shipping different content is a stronger one, because no upgrade explains it. An ordinary version bump is inventory and scores nothing. It records the subset rather than the file because the file churns and the subset does not — measured over 57 lockfile revisions ofaxios/axios,npm/cliandnodejs/undici, the subset moved twice and the same version was never republished. A workspace package is identified by its name rather than its location (v0.34.0+), so movingpackages/clitoapps/cliis not a dependency that started running code at install; the recorded baseline says which naming it holds, so upgrading to that version costs one comparison rather than inventing a burst of findings. The read has its own 4 MiB ceiling (v0.34.0+) instead of sharing the obfuscation analysis's — measured against thirteen real lockfiles, two of them above the old one. npm only, and the report says so: pnpm dropped its build declaration at lockfileVersion 9 and Yarn never had one, so a repository whose only lockfile is theirs gets an explicit line saying its dependency surface was not compared — as does one with no lockfile, one whose lockfile is too large to read, and one whose schema octoscope has not measured. octoscope never looks at the registry: the claim is that your dependencies' auto-execute surface changed, never that a dependency is malicious.It also reports your capability footprint (v0.27.0+) — what a compromise of the repo could reach. Workflow permissions and triggers are read from the files it already fetched, plus self-hosted runners, write deploy keys and off-platform webhooks. Holding power is not itself a finding: a release workflow with
contents: writeon a tag push is normal and stays inventory. What scores is power reachable from untrusted input — apull_request_targetworkflow holding the repo's secrets, or a self-hosted runner an outsider's pull request can reach. The probes that need admin scope fail open on a minimal token, and the report says which ones it could not check.Since v0.28.0 it stops taking a workflow file at its word. Reusable-workflow chains are followed: a fork-triggered workflow holding nothing that calls one reading a secret is one path to that secret, not two harmless files, and the finding names the caller an outsider comes through. Power travels the other way — a called workflow holds what its caller granted, never more, so one whose caller hands over nothing is not credited with anything it declares. And a workflow that declares no
permissions:runs with your repository's default, which an owner can widen to read/write; octoscope reads that setting and joins it to the file, and where it cannot, says so rather than deciding the workflow holds nothing. Where a chain leaves what the scan can see (v0.30.0+) — a call into another repository, or one into this repository the scan did not read — it says so, always, not only where an outsider could reach the caller. It does not follow the call, and an unfollowed chain that reads like one which ended safely is the failure this axis exists to prevent. One line for the scan, naming where those workflows live.
The footer surfaces your GitHub GraphQL budget live:
Updated 12s ago · rate 4872/5000 · reset 23m · auto 60s
The chip is muted at normal levels, warn-yellow under 20% remaining, and error-red under 5%. If a refresh runs into GitHub's primary rate limit, the auto-refresh backs off until the reset time GitHub last reported instead of hammering every 60s — whether that refresh was automatic or yours; after a secondary limit it waits one interval, and at least 60 seconds. It reacts to the limit's error, not to the budget counter: a refresh that succeeds with nothing left still schedules the next one at the interval, and a session whose very first fetch already hits the primary limit has no reset time yet, so it retries at the interval.
When a refresh fails, the dashboard stays on screen and the footer says
why — rate-limited · retry at 14:23, token rejected · check $GITHUB_TOKEN, offline · retrying, or github errored · retrying —
so you know whether to wait, fix auth, or check the network, followed by
how old what you are looking at is (updated 2m5s ago). The full-screen
error appears only when there is nothing to show yet (v0.39.0+; before,
any failed refresh replaced the dashboard with it).
Transient GitHub hiccups — a 502 from the GraphQL gateway, an HTTP/2
stream error mid-flight — are retried automatically a few times with a
short backoff before the error ever reaches the screen (v0.17.0). The
full-screen error view now reads as a clean, human sentence instead of a
raw stack of 502 Bad Gateway HTML, so a passing blip no longer looks
like a broken app.
Press % (v0.18.0+) for the rate-limit detail panel: a
per-resource breakdown of every REST + GraphQL budget — used, remaining
and reset time for graphql, core, search and the rest — straight
from GitHub's /rate_limit endpoint, which is free and consumes no
quota. The footer chip tells you how you're doing; the panel tells
you why. r refreshes it, esc closes.
Pass --public-only to hide private repositories, PRs and issues from
the lists — perfect for demos, screenshots and screencasts where you
don't want internal work leaking. Global counters (PRs Authored, PRs
Merged, Issues Authored) stay complete; only titles and repo names get
filtered.
The Activity tab has two halves at different zoom levels, switched with
← / → (or h / l): the heatmap answers how much, the feed answers
what.
The last ~52 weeks of your contribution calendar, shaded on an accent-pink gradient that adapts to your own distribution (the busiest day always hits the full neon pink, the quiet days sit on the surface grey). Underneath:
- Total contributions for the window
- Current streak (how many consecutive days you've pushed)
- Longest streak in the window
- Busiest day with its date, so you know when you shipped the most
A timeline of your recent events — pushes, pull requests opened and merged,
reviews, issues, releases, branches created and deleted — newest first,
across every repository at once. enter opens the row's subject on GitHub,
c copies its link, / filters on anything the row shows (a repo name, a
verb like merged, part of a title), and r reloads.
Four things are worth knowing, because they are properties of GitHub's events API rather than choices:
- It is genuinely recent, not a history. GitHub keeps a limited window and octoscope reads one page of 100 events from it. On a busy account that page can cover as little as two days. The line under the table always names the span it actually got, and says when it is at the cap.
- Review and comment traffic on the same subject is folded into one row
with a
×Ncount, so a heavily-reviewed pull request does not bury everything else. Only adjacent events on the same subject fold, and anything that changed state (opened, merged, closed, pushed, released, approved, changes requested) always keeps its own line. The page is sorted newest-first before any of that, because GitHub's feed is not in chronological order — which is also what makes the folding work, since "adjacent" only means something on an ordered list. - Pull-request rows show a title when one can be found. GitHub sends a truncated pull-request object with no title in it; where the same page contains a comment on that pull request, the title comes from there. When it does not, the row shows the bare number rather than inventing a label.
- It loads when you open it, not on every dashboard refresh — GitHub
asks callers to poll this endpoint no more than once a minute, and
--refreshgoes as low as 5s.
Under --public-only the feed asks GitHub's public-events endpoint, so
events in private repositories are never fetched at all; toggling the mode
with p mid-session drops them from what is already on screen. The
visibility column only appears when there is a mix to distinguish.
- Change pulse — whenever a value changes between two refreshes (e.g. a new star arrives, someone follows or unfollows you, an issue gets closed), the affected card's border flashes accent-pink for 2 seconds.
- Native notifications — Stars and Followers changes also trigger a
system notification and a short audio beep, so you notice the "passive"
events even when octoscope is in a background tab. Clicking the banner
opens the relevant page (your profile for follower changes, the
starred-repos tab for star changes).
- macOS: notifications go through
terminal-notifier, which the Homebrew formula installs automatically as a dependency. If you installed octoscope another way (go install, manual binary), runbrew install terminal-notifieronce. The click-through works; the custom icon does not — Apple deprecatedNSUserNotificationin macOS 11, and the system now ignores-appIconoverrides for notifications coming from CLI tools. The banner shows theterminal-notifiericon instead. Cosmetic only. - Linux & Windows: notifications carry the embedded octoscope icon. Click activation depends on your DE / shell — best-effort.
- macOS: notifications go through
- Instant first paint (v0.39.0+) — the dashboard you saw last opens
at once, marked
cached 3h ago · refreshing…in the footer, while the live fetch runs (about ten seconds on a busy account). With no network it stays up and saysoffline · retrying · cached 3h ago. The snapshot is the last successful fetch; a launch starts from it only while it is under a week old, only on the octoscope version that wrote it, and only for the same token and account. It lives in your user cache folder (~/Library/Caches/octoscopeon macOS,~/.cache/octoscopeon Linux,%LocalAppData%\octoscopeon Windows) in a file only you can read, because it holds what the dashboard shows, private repository names included. Never read or written under--public-only, nor by--json,--plainor--scan. Opt out withdashboard_cache = false, which also deletes it. - Update notice — on launch (and hourly) octoscope checks whether a
newer release is out and, if so, shows a quiet line under the banner
with the right upgrade command for how you installed it (
brew,go install,gh extension, or a download link). It never self-updates — the package manager owns the binary. Turn the check off withcheck_for_updates = false; it's also suppressed under--public-only. - GitHub's own status (v0.30.0+) — at launch, on a manual refresh and
when a fetch fails, octoscope asks githubstatus.com whether a problem
is GitHub's, and says so instead of leaving you to guess. Silent while GitHub is healthy, silent if the
status page can't be reached, and quiet about incidents that don't
touch anything octoscope uses. Never polls. Opt out with
check_service_status = false. - Actionable auth errors (v0.23.0+) — an expired or revoked token
says so and names the fix for where the token came from:
$GITHUB_TOKENpoints at the regenerate URL, aghCLI login points atgh auth refresh. A token that's valid but missing a scope names the scopes GitHub asked for. - Watched-entry notices (v0.23.0+) — a
watch_reposentry that no longer resolves (renamed, deleted, gone private) surfaces as a muted "N watched entries skipped" line under the Repos tab naming the stale refs, instead of vanishing silently. Transient network blips still pass quietly.
Some things you can see on your GitHub profile page are not exposed by the GitHub GraphQL or REST API, so octoscope doesn't show them:
- Achievements (Pull Shark, Starstruck, YOLO, …)
- Highlights like the PRO badge
- The local time next to the location field
Supporting any of these would require scraping the profile HTML, which we don't do.
octoscope runs on macOS (Apple silicon and Intel), Linux (arm64 and x86_64) and Windows (x86_64). Homebrew is the recommended path on macOS and Linux; every release also carries an archive for each system (below).
brew install gfazioli/tap/octoscopebrew upgrade gfazioli/tap/octoscope picks up newer versions as they
ship.
Since v0.34.0 the tap ships a cask rather than a formula — the command above is unchanged, and it serves Linux Homebrew as well as macOS. If you installed before that, the one-time move is:
brew uninstall octoscope
brew install --cask gfazioli/tap/octoscopeInstalled 0.34.0 and octoscope does nothing? That release's cask
installed a binary macOS refused to launch — a cask's download is
quarantined where a formula's is not, and Gatekeeper will not run an
ad-hoc-signed binary under quarantine. It was killed at startup and the
file removed, so the command printed nothing and exited 137. Upgrading
fixes it:
brew upgrade --cask gfazioli/tap/octoscopeEvery release
carries octoscope_<version>_macOS_arm64.tar.gz and _macOS_x86_64.tar.gz,
_Linux_arm64.tar.gz and _Linux_x86_64.tar.gz, and
_Windows_x86_64.zip. Each holds the binary with the README and the licence:
put octoscope (octoscope.exe on Windows) on your PATH. The
site's Download button picks the one
for your machine.
go install github.com/gfazioli/octoscope@latestRequires Go 1.26.9 or later.
gh extension install gfazioli/gh-octoscope
gh octoscopeThe same binary from the same release, reachable without leaving gh.
It lives in a second repository
only because gh derives an extension's name from the repository name and
refuses anything not prefixed gh- — there is nothing else in it, and
issues belong here.
The image is for the scriptable half of octoscope, not the dashboard: a
TUI in a container has nothing attached to read it, while --json and
--plain fetch once, print and exit.
docker run --rm -e GITHUB_TOKEN ghcr.io/gfazioli/octoscope:latest --plainMulti-arch (linux/amd64, linux/arm64), ~7 MB, and it runs as an
unprivileged user. -e GITHUB_TOKEN with no value passes the variable
through from your shell rather than baking it into the command line, where
it would be visible in ps and in your shell history.
In a CI step, piping into jq:
- name: Publish account stats
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -o pipefail
docker run --rm -e GITHUB_TOKEN ghcr.io/gfazioli/octoscope:latest --json \
| jq -r '"followers=\(.social.followers) stars=\(.social.total_stars)"'With no token it exits 1 with an empty stdout — but a pipeline only
notices with pipefail set, otherwise the exit status is jq's and jq
is perfectly happy with no input. GitHub Actions does not set it for
you unless the step says shell: bash; the default is bash -e {0}.
Hence the line above, which travels to any CI.
Useful fields for a gate: .rate_limit (remaining, reset_at),
.review_requests, .activity.
Run it with no flag at all and it refuses rather than hanging — the dashboard needs a terminal the container does not have:
octoscope: could not open a new TTY: open /dev/tty: no such device or address
Download the right platform archive from the
latest GitHub Release,
unpack it, and drop the octoscope binary anywhere on your $PATH.
Each release also carries the bare executables next to the archives, named
octoscope_<version>_<os>-<arch>, for when unpacking is the awkward part:
VERSION=0.39.0 # or whatever the latest release says
curl -fsSL -o octoscope \
"https://github.com/gfazioli/octoscope/releases/download/v${VERSION}/octoscope_${VERSION}_linux-amd64" \
&& chmod +x octoscopeOn macOS both the archives and the bare executables are signed with a
Developer ID certificate and notarized by Apple, so a downloaded binary
runs without Gatekeeper refusing it. A notarization ticket cannot be
embedded in a bare executable, though, so macOS verifies it online the
first time the binary runs — that one run needs a network connection.
Before v0.34.3 these builds carried only Go's own ad-hoc signature, which
macOS refuses to run once the file has been flagged com.apple.quarantine
— what a browser download and a Homebrew cask both do. Fetching with
curl, which sets no such flag, worked before and works now.
The version appears in the asset's own name, so the releases/latest/
shortcut cannot be used here — it resolves the newest release but still
asks for the file name you typed, which 404s the moment a newer one
ships.
octoscope # your dashboard (requires a token)
octoscope <username> # any public profile (token optional)
octoscope --refresh 30s # auto-refresh every 30 seconds
octoscope --compact # dense card layout for narrow terminals
octoscope --public-only # hide private repos/PRs/issues (safe for demos)
octoscope --no-sponsor # skip the sponsor splash for this run
octoscope --theme phosphor # 80s green CRT theme — see Themes section
octoscope --theme list # preview all built-in palettes and exit
octoscope --no-color # force the monochrome theme (or set NO_COLOR)
octoscope --plain # static text summary, no TUI
octoscope --json # machine-readable JSON, no TUI
octoscope --json --activity # ... plus the recent-activity feed
octoscope --plain --inbox # ... plus your unread notifications
octoscope --scan # supply-chain sweep of every repo, then exit
octoscope --scan --json # ... as JSONExamples:
octoscope # you
octoscope torvalds # Linus Torvalds
octoscope gvanrossum # Guido van Rossum
octoscope gfazioli # the author
octoscope --public-only # you, but screenshot-safeoctoscope can run non-interactively: it fetches the dashboard once, prints it, and exits without ever entering the TUI. Two modes:
--plain— a human-readable text summary (profile counters plus the key lists), colourless and safe for shell status-lines and quick checks. Its exact layout is not a contract and may change between releases.--json— the same data as JSON, for piping intojq, cron jobs, or a status-line generator.
Both honour --public-only and the usual auth cascade
($GITHUB_TOKEN → gh auth token). They are mutually exclusive.
A server error from GitHub — a 500, 502, 503 or 504, typically
the 502 its gateway answers on a bad moment — or a dropped HTTP/2
stream is tried up to three times in all, with a short backoff between
attempts, exactly as the dashboard does, so a scheduled run rides out
the same blips (since v0.38.0; before, the first one failed the run). A
rejected token, a rate limit, a user that does not exist or a network
timeout fails at once. Either way a failed run exits with status 1,
the reason on standard error and nothing on standard output.
--activity adds the recent-activity feed — the same events the
TUI's Activity tab shows — to either mode. It is opt-in because it
costs one extra API request, and most scripted runs only want the
counters. Passing it without --plain or --json is a usage error
rather than a no-op: the TUI always shows that tab anyway.
--inbox does the same for the Inbox tab: your unread notification
threads, newest first, one page of up to 50 — the page the tab loads. It
is a flag of its own rather than part of --activity because it is a
different endpoint with a different failure: GitHub's notifications
endpoint does not accept a fine-grained token (see
Token scopes), so a script on one would fail on
--inbox alone. It is always your inbox, so it cannot be combined with
a username, and --public-only leaves out threads from private
repositories.
octoscope --json | jq '.social.total_stars'
octoscope --json --public-only > snapshot.json
octoscope --json --activity | jq '.recent_activity[0]'
octoscope --json --inbox | jq '[.inbox[] | select(.reason == "review_requested")] | length'
octoscope torvalds --plainThe --json output is a versioned contract: the top-level
schema_version (currently 1) is bumped only on a breaking change
(a renamed, removed, or retyped field). New fields may be added without
a bump, so pin your consumers to schema_version and treat unknown
keys leniently. Every list is always an array (never null), so you
can iterate unconditionally.
recent_activity and inbox are the two lists that are absent
rather than empty when they have nothing to say, and the distinction
carries information: no key at all means --activity (or --inbox) was
not passed, while [] means it was and the account has no recent events
(or no unread notifications). Same reasoning as commits_last_year on a
repository. Rows are newest first — an order octoscope imposes, because
GitHub's feed does not have it (see #184). An inbox thread's reason and
type are GitHub's own words and open sets, passed through verbatim;
unread is true for every row, since only unread threads are fetched.
ci_state, latest_release, rate_limit, a repository's
commits_last_year and monthly_sponsors_income_cents are omitted when
empty / unavailable — so none of them can ever be read back as a zero. The
sample shows monthly_sponsors_income_cents with a value for that reason:
a 0 there is not a state the encoder can produce. A repository's
commits_last_year appears only when the opt-in commit_counts branch
ran, which is why it is a pointer rather than an int. Lists
follow the same caps as the TUI (repositories up to 100, PRs / issues up to
50, sponsors up to 20 per direction).
The sponsor totals are separate from the lists on purpose: both lists are
capped, so comparing len(sponsors) against "how many sponsors do I have"
is wrong on a busy account — read sponsors_total. And
monthly_sponsors_income_cents is only ever present for the account the
token belongs to.
The security scan answers "is this repository
compromised?" one repository at a time, from the dashboard. --scan
(v0.38.0+) answers "is anything of mine?": it runs the same scan on the
default branch of every repository you own, then of every entry in
your watch_repos list, so an organisation's repositories are included
the way they are in the dashboard — prints a report and exits.
$ octoscope --scan
octoscope 0.38.0 — supply-chain sweep
68 repositories · default branch only · 15.3 s · generated 2026-10-09T09:23:46Z
likely compromised 0
suspicious 1
watch 0
clean 66
not scanned 1
Suspicious
you/some-repo score 5
+5 provenance tip 0ee954a forged as "github-actions[bot]" but not signed by GitHub; unsigned bot commits changed .vscode/tasks.json
Not scanned
you/new-repo the repository has no commits yet
What it does not do, by design:
- It reads the default branch only. A compromised side branch is the on-demand scan's job, which walks up to 20 branches. Run that one on anything the sweep flags.
- It does not compare with earlier scans and records no baseline: a default-branch-only fingerprint would make the next full scan report the other branches as gone.
- A repository it cannot read is never counted clean. It is listed as not scanned, with why — a watched entry the dashboard could not resolve included. And a file it matched but could not read, a hook whose content did not arrive, is listed with its repository whatever the verdict, because its obfuscation was not checked. Lockfiles are the exception: everything the scan reads in one is a comparison with an earlier scan, which the sweep does not make.
It needs a token (it sweeps your repositories, so it refuses a username),
runs up to ten repositories at a time, retries a transient GitHub 5xx,
and honours --public-only: it names a repository only when GitHub said
it is public — in the dashboard fetch or in the repository's own scan,
the later answer winning. One read as private is left out, and a watched
entry neither call could place is counted in watched_left_out rather
than named. A progress line goes to standard error only
when that is a terminal, so a cron job stays quiet. It cannot be combined
with --activity, --inbox or --theme list.
--scan --json is a contract of its own, versioned separately from the
dashboard report (schema_version: 1):
{
"schema_version": 1,
"octoscope_version": "0.38.0",
"generated_at": "2026-10-09T09:23:46Z",
"public_only": false,
"scope": "default_branch",
"elapsed_seconds": 15.3,
"watched_left_out": 0,
"summary": {
"repositories": 68, "likely_compromised": 0, "suspicious": 1,
"watch": 0, "clean": 66, "not_scanned": 1
},
"repositories": [
{
"repository": "you/some-repo",
"url": "https://github.com/you/some-repo",
"scanned": true,
"verdict": "suspicious",
"score": 5,
"default_branch": "main",
"partial": false,
"findings": [
{ "axis": "provenance", "weight": 5,
"reason": "tip 0ee954a forged as \"github-actions[bot]\" but not signed by GitHub; unsigned bot commits changed .vscode/tasks.json" }
],
"unchecked": [
{ "name": "deploy keys", "reason": "the token lacks the scope this needs" }
],
"unread_files": []
},
{
"repository": "you/new-repo",
"url": "https://github.com/you/new-repo",
"scanned": false,
"reason": "the repository has no commits yet",
"partial": false,
"findings": [],
"unchecked": [],
"unread_files": []
}
]
}Every repository is in exactly one summary count, so they add up to
repositories. A repository with scanned: false has a reason and no
verdict or score; score is present, 0 included, on every scanned
one. findings lists the scored evidence only, heaviest first, and
verdict is clean, watch, suspicious or likely_compromised —
the summary key's spelling. unchecked names the capability probes
that could not run, and unread_files the matched files whose content
the scan did not read (lockfiles excepted, as above), each with its
reason — a clean verdict without them is a narrower claim. partial means GitHub returned
the branch's tree truncated. Lists are always arrays, never null.
octoscope --scan --json | jq '.summary'
octoscope --scan --json | jq -r '.repositories[] | select(.verdict == "suspicious" or .verdict == "likely_compromised") | .repository'octoscope ships with seven built-in themes. Pick one with
--theme NAME, the theme config key, or cycle through them live
in the in-app settings panel (, → arrow down to Theme → ← /
→). The panel also edits the accent colour override and the
sponsor splash toggle (v0.26.0) — the last two config keys that
were previously file-only.
| Theme | Vibe |
|---|---|
octoscope |
Default — pink + cyan |
high-contrast |
Pure white accent, ANSI brights — maximum legibility |
terminal |
Inherits from your emulator's palette (ANSI 8-15) |
monochrome |
All-greys, zero chroma |
stranger-things |
Crimson + Christmas-lights yellow on an "Upside Down" muted |
phosphor |
80s P1/P31 CRT pure green — vt100 vibe |
amber |
80s amber CRT (IBM 5151, WordStar) |
Three of them, side by side:
stranger-things |
phosphor |
terminal |
|---|---|---|
![]() |
![]() |
![]() |
The terminal theme is special: it picks colours from the ANSI
brights of your emulator's palette, so it follows your iTerm /
Ghostty / Alacritty colour scheme automatically. The screenshot
above is what terminal looks like with the author's iTerm palette
— yours will differ.
You can override just the accent colour while keeping the rest of a
theme via the accent_color config key (or --theme plus an
accent_color in the file). Any value lipgloss accepts works: hex
like "#FF0080" or ANSI 256 numbers like "201".
The octopus on the launch screen, and the smaller one that stays beside
the dashboard's banner (both since v0.36.0), are drawn in the theme too:
body in the accent, eyes and periscope lens in the value colour, so an
accent override recolours them. Monochromatic themes — and NO_COLOR,
which forces one — draw them as a one-colour silhouette.
octoscope honours the NO_COLOR convention
(since v0.22.0): when the NO_COLOR environment variable is present and
non-empty (its
value doesn't matter), or you pass --no-color, octoscope forces the
zero-chroma monochrome theme, overriding any --theme / theme
config value and dropping the accent override. It's an environment
directive for that run only — the theme / accent_color keys saved
in your config file are left untouched, so your real theme comes back
the moment NO_COLOR is unset.
octoscope reads ~/.config/octoscope/config.toml on startup (honours
$XDG_CONFIG_HOME when set). All keys are optional; missing keys fall
back to defaults. CLI flags override the file. The file is not
created automatically — write it yourself when you want to customise.
# ~/.config/octoscope/config.toml
# Auto-refresh interval. Go duration syntax: "30s", "1m", "5m", "1h".
# 0 / negative falls back to 1m; values below 5s are raised to 5s so a
# typo can't hammer the GitHub API.
refresh_interval = "1m"
# Hide private repositories, PRs and issues from the list tabs.
# Useful if you screenshot or screencast octoscope often. Global
# counters (PRs Authored, Issues Authored, ...) stay complete since
# they're aggregate numbers, not titles.
public_only = false
# Use the dense card layout in the Overview tab — smaller cards,
# abbreviated labels. Fits more onto narrow terminals.
compact = false
# Visual theme. Built-in: octoscope (default), high-contrast,
# terminal, monochrome, stranger-things, phosphor, amber.
theme = "octoscope"
# Initial view preferences (v0.23.0+). One sort key seeds every tab
# whose sort cycle has that column: pushed | stars | forks | name |
# ci | release | commits (needs commit_counts) apply to the Repos tab;
# updated | repo | number apply
# to the PRs and Issues tabs. Unset keys keep the built-in defaults
# (pushed / updated, no work filter, density sparkline).
default_sort = "pushed"
# Repos work filter preset (the `w` cycle): "prs-open" | "ci-broken"
# | "stale". Empty = no filter.
default_work_filter = ""
# Star-history sparkline mode in the repo drill-in (the `v` cycle):
# "density" | "cumulative".
default_star_history = "density"
# Show the sponsor splash at launch (v0.16.0+). Set to false to opt
# out, or pass --no-sponsor for a single run. Always suppressed under
# --public-only so screenshots stay clean.
show_sponsor = true
# Check for a newer octoscope release on launch + hourly, and show a
# small notice under the banner when one exists (v0.19.0+). octoscope
# never self-updates — it only suggests the upgrade command for how you
# installed it. Set to false to disable the check entirely.
check_for_updates = true
# Ask GitHub's own status page (www.githubstatus.com) whether GitHub is
# healthy, so an outage on their side doesn't look like an octoscope bug.
# Unauthenticated, costs nothing against your rate limit, and never
# polls. Silent unless something octoscope depends on is unhealthy — and
# silent too if the status page can't be reached, because octoscope never
# claims a clean state it hasn't verified. This is the only feature that
# contacts a host other than api.github.com; set false to opt out.
check_service_status = true
# Add a "commits by you, last year" column to the Repos tab (v0.32.0+).
# Off by default: it is the one column that costs a query of its own on
# every refresh — GitHub counts each repository's history on request —
# and it needs an authenticated viewer to count for. Sortable with s;
# also a toggle in the settings panel (,).
commit_counts = false
# Keep the dashboard's last snapshot on disk so the next launch paints at
# once, marked "cached", and refreshes in the background (v0.39.0+). The
# file holds what the dashboard shows, private repository names included,
# and only you can read it. Never used under --public-only, --json,
# --plain or --scan. Set false to never write one; any already written is
# deleted at the next launch.
dashboard_cache = true
# Optional override for just the accent slot of the active theme.
# Hex ("#FF0080") or ANSI 256 ("201"). Leave unset to keep the
# theme's default accent.
# accent_color = "#FF0080"
# Repositories pinned to the top of the Repos tab. Each entry is
# "owner/name"; order is preserved exactly as listed here. Press
# P on any repo row to add / remove it — the file is rewritten
# atomically. Malformed entries are dropped silently at load.
# pinned_repos = [
# "gfazioli/octoscope",
# "gfazioli/Mantine-Hint",
# ]
# Issues pinned to the top of the Issues tab (v0.21.0+). Each entry
# is "owner/name#N"; order is preserved exactly as listed here. Press
# P on any issue row to add / remove it — the file is rewritten
# atomically. Malformed entries are dropped silently at load, and a
# pinned issue that gets closed simply stops showing (the entry is
# harmless).
# pinned_issues = [
# "gfazioli/octoscope#42",
# "charmbracelet/bubbletea#1234",
# ]
# External repositories to monitor in a Watched section under
# the Repos tab (v0.14.0+). Hand-edit only — there is no
# in-app toggle. Each entry resolves to its own GraphQL query
# at refresh time; failures (404, private, network blip) are
# dropped silently so a stale entry doesn't break refresh.
# watch_repos = [
# "charmbracelet/bubbletea",
# "cli/cli",
# ]Pass --config PATH to read a different file (handy for trying out
configs without touching the default one).
A malformed TOML file makes octoscope exit with an error so you notice the typo straight away — there's no silent fallback to defaults when the file is present but broken.
You don't have to drop to a shell to tweak settings: press ,
(comma) while octoscope is running and a settings panel opens.
Use ↑ / ↓ (or Tab) to move between rows, space to flip a
toggle, ← / → to cycle the theme picker, type to edit the
refresh field, Enter to save, Esc to cancel.
What you change applies live and instantly: a new
refresh_interval reschedules the auto-refresh tick, compact
re-renders, public_only filters the lists on the spot, and
theme rebuilds every style on save so the dashboard repaints in
the new palette without a restart. The panel persists changes back
to your config file (the default path or whatever you passed to
--config), so the next launch picks them up too.
For the most common toggle, you also get a single-key shortcut
outside the panel: hit p from any tab and public-only flips
state immediately, with the file updated alongside. A yellow
◐ public-only badge next to authenticated in the profile
card makes the current mode unmissable at a glance.
Key bindings while running:
| Key | Action |
|---|---|
1-8 |
Jump to tab (Overview, Repos, PRs, Issues, Activity, Gists, Inbox, What's new) |
tab / shift+tab |
Cycle tabs forward / backward |
↑ / ↓, j / k |
Move cursor in list tabs and the Activity feed · scroll Overview / Activity heatmap when the body overflows the window |
pgup / pgdn, u / d |
Page up / down on Overview & Activity (vertical scrolling) |
space |
On Repos / PRs / Issues / Gists / Inbox: open the action menu for the selected row · on Overview / Activity: page down |
enter (Activity feed) |
Open the event's subject on GitHub — the pull request, issue, comment, release or compare view |
g / G |
Jump to top / bottom |
s |
Cycle sort column · on Inbox: cycle the filter (all / involving you / ci) |
/ |
Filter by substring |
w |
On the Repos tab: cycle the work filter (PRs open → CI broken → stale 90d → off) — composes with /, spans pinned / owned / watched sections |
v |
Inside a repo's detail view: toggle the star-history sparkline between weekly density and cumulative growth |
% |
Open the rate-limit panel: per-resource REST + GraphQL budget breakdown (used / remaining / reset) from GitHub's free /rate_limit endpoint |
enter |
On Repos / PRs / Issues: open the drill-in detail view for the selected row · in any menu: confirm the highlighted action · in the PR files-list sub-view: open the file's diff |
o |
Open the selected repo / PR / issue in your browser (or, inside the PR diff viewer, the PR's Files-changed tab on github.com) |
c |
Copy the selected row's URL (or, inside the PR diff viewer / files list, the current file's path) to your system clipboard |
f |
Inside the PR drill-in: inspect the changed files (full-screen list → Enter opens each file's diff with syntax highlighting) |
P |
On a Repos or Issues row: toggle pin/unpin (pinned rows stick to the top of that tab, ordering preserved across refreshes; writes back to config) |
o / d / c / P |
Inside the action menu on Repos or Issues: Open in GitHub · View details · Copy URL · Pin/Unpin |
esc |
Close the action menu / detail view, or clear the current filter |
r |
Refresh now (or refetch the current detail view when open) |
p |
Toggle public-only mode (saves to config) |
, |
Open the in-app settings panel |
? |
Open the keyboard-shortcut overlay (any key to dismiss) |
← / →, h / l |
On the Activity tab: switch between the heatmap and the feed |
← / → |
Cycle theme (when the Theme row is focused in the settings panel) |
q |
Quit |
ctrl+c |
Quit |
(Pass --help for the full list of CLI flags and their defaults.)
octoscope resolves a GitHub token from, in order:
$GITHUB_TOKENenvironment variablegh auth token— if the GitHub CLI is installed and logged in- No token — falls back to the unauthenticated GitHub rate limit (60 req/h)
Rules of thumb:
- Viewing your own account (
octoscopewith no arg) requires a token — there's no "viewer" to resolve without one. - Viewing any other user (
octoscope <username>) works with or without a token, but without one the unauthenticated 60 req/h limit gets burned through fast at the default 60-second refresh interval.
A token is effectively required if you plan to keep the dashboard open for more than a few minutes regardless of whose profile you're viewing.
octoscope is read-only — it never mutates anything on your account. The minimal scopes it needs depend on which kind of token you mint:
Fine-grained personal access token (recommended). All permissions are read-only:
- Repository permissions
Metadata— Read (mandatory)Contents— ReadIssues— ReadPull requests— ReadAdministration— Read (only if you want traffic in the repo drill-in: GitHub files views and clones under it. Without it the section says so on repositories you can push to, and stays absent everywhere else)Dependabot alerts— Read (only if you want the repo drill-in's Dependabot alerts: without it the section says GitHub refused the token where you can push, and stays absent everywhere else)
- Account permissions
Profile— ReadFollowers— ReadEmail addresses— Read (only if you want the email field on the profile card)
Under Repository access pick All repositories (or just the ones you want to see).
One exception, and it is GitHub's rather than octoscope's. The Inbox tab and the
--inboxflag read/notifications, and GitHub documents that endpoint as supporting "only … a personal access token (classic)" — so a fine-grained token cannot load it, whatever permissions you grant. Every other tab works normally; the Inbox says what happened rather than failing silently, and so does--inbox. If you want it, mint a classic token withnotificationsorrepo.
Classic personal access token:
read:user— profile, followers, social accountsnotifications— the Inbox tab.repoalso covers it, so if you already haverepothere is nothing to addrepo— required to see your private repos / PRs / issues, and what lets the repo drill-in show traffic and Dependabot alerts (GitHub documentssecurity_eventsfor the alerts;repoalone reads them, measured withgh's default token). Drop it if you only care about public content; the dashboard still works and just hides private items.read:org— only needed if you're a member of orgs with private membership and want them under Network. Public org memberships show up without it.
Via gh auth token: if the GitHub CLI is
already logged in (gh auth login), octoscope picks up that token
automatically — the default gh scopes already cover everything.
octoscope never needs write:*, delete_repo, admin:*, gist, or
any workflow scope.
Bug reports and ideas are welcome via issues. Pull requests, too — please open an issue first for anything non-trivial so we can agree on the shape before code lands.
Questions, ideas, a bug you want to talk through, or what comes next: join the Undolog Discord, where octoscope lives alongside its maker's other tools.
Your support helps me:
- Keep the project actively maintained with timely bug fixes and security updates
- Add new features, improve performance, and refine the developer experience
- Expand test coverage and documentation for smoother adoption
- Ensure long-term sustainability without relying on ad hoc free time
- Prioritize community requests and roadmap items that matter most
Open source thrives when those who benefit can give back—even a small monthly contribution makes a real difference. Sponsorships help cover maintenance time, infrastructure, and the countless invisible tasks that keep a project healthy.
Your help truly matters.
💚 Become a sponsor today and help me keep this project reliable, up-to-date, and growing for everyone.
Since v0.16.0, octoscope shows a small sponsor splash at launch (press
oto open the page,cto copy the link, or any other key to dismiss). It's suppressed automatically under--public-only. To turn it off, setshow_sponsor = falsein your config, or pass--no-sponsorfor a single run.On terminals that support OSC 8 hyperlinks (iTerm2, WezTerm, Kitty, recent VS Code), the splash link is clickable directly — no need to copy/paste the URL (v0.17.0).
MIT — see LICENSE.



{ "schema_version": 1, "octoscope_version": "0.24.1", "generated_at": "2026-07-08T12:00:00Z", "authenticated": true, "is_viewer": true, "public_only": false, "profile": { "login": "...", "name": "...", "bio": "...", "company": "...", "location": "...", "created_at": "..." }, "social": { "followers": 0, "following": 0, "total_stars": 0, "total_stars_with_forks": 0 }, "activity": { "prs_total": 0, "prs_merged": 0, "issues_authored": 0, "open_prs_authored": 0, "commits_last_year": 0, "contributed_repos_last_year": 0 }, "operational": { "public_repos": 0, "forks_received": 0, "open_issues": 0, "open_prs": 0 }, "languages": [ { "name": "Go", "bytes": 0, "percent": 0.0 } ], "repositories": [ { "name": "...", "url": "...", "language": "...", "stars": 0, "forks": 0, "open_issues": 0, "open_prs": 0, "pushed_at": "...", "private": false, "ci_state": "SUCCESS", "commits_last_year": 0, "latest_release": { "tag": "...", "published_at": "..." } } ], "open_pull_requests": [ { "number": 0, "title": "...", "repo": "owner/name", "url": "...", "draft": false, "mergeable": "MERGEABLE", "updated_at": "...", "private": false } ], "open_issues_list": [ { "number": 0, "title": "...", "repo": "owner/name", "url": "...", "updated_at": "...", "private": false } ], "review_requests": [ /* same shape as open_pull_requests, plus "author" */ ], "organizations": [ { "login": "...", "name": "..." } ], "gists": [ { "name": "...", "label": "...", "description": "...", "url": "...", "public": true, "fork": false, "stars": 0, "files": [ "a.go" ], "files_capped": false, "updated_at": "..." } ], "sponsors": [ { "login": "...", "name": "...", "url": "...", "is_org": false } ], "sponsors_total": 0, "sponsoring": [ /* same shape as sponsors */ ], "sponsoring_total": 0, "has_sponsors_listing": false, "monthly_sponsors_income_cents": 1234, "watched_repos": [ /* same shape as repositories */ ], "watched_skipped": [ "owner/renamed" ], "rate_limit": { "cost": 0, "limit": 5000, "remaining": 0, "reset_at": "..." }, // --activity only. Absent otherwise — see below. "recent_activity": [ { "id": "...", "type": "PullRequestEvent", "repo": "owner/name", "created_at": "...", "public": true, "action": "merged", "ref": "", "ref_type": "", "number": 0, "is_pull_request": true, "title": "...", "url": "..." } ], // --inbox only. Absent otherwise, like recent_activity. "inbox": [ { "id": "...", "reason": "review_requested", "type": "PullRequest", "title": "...", "repo": "owner/name", "url": "...", "unread": true, "updated_at": "...", "private": false } ] }