doc/github_stats.txt — rendered from the plugin's own vimdoc
*github_stats.txt* GitHub Traffic Statistics Collector for Neovim *github_stats.nvim*
CONTENTS
1. Introduction ........................... |github_stats-introduction| 2. Requirements ........................... |github_stats-requirements| 3. Installation ........................... |github_stats-installation| 4. Configuration .......................... |github_stats-configuration| 5. Commands ............................... |github_stats-commands| 6. Dashboard UI ........................... |github_stats-dashboard| 7. Visualization .......................... |github_stats-visualization| 8. Export ................................. |github_stats-export| 9. Diff ................................... |github_stats-diff| 10. Date Presets ........................... |github_stats-date-presets| 11. API .................................... |github_stats-api| 12. Storage ................................ |github_stats-storage| 13. Digest ................................. |github_stats-digest| 14. Troubleshooting ........................ |github_stats-troubleshooting|
INTRODUCTION
GitHub Stats is a Neovim plugin for automatic collection and analysis of GitHub repository traffic statistics.
Features:
• Silent background data collection (session-persistent, no messages
unless something actually fails)
• Auto-discovered repos: track every public repo of a GitHub user with
one line (watch_users), on top of any explicitly listed repos
• Flexible configuration (setup() or config.json)
• Historical JSON storage with custom paths
• Smart date defaults in commands
• Time-range based analytics
• ASCII visualizations (sparklines, charts)
• Export to CSV and Markdown
• Period comparison (diff mode)
• Async-first (no UI blocking)
• Floating windows for results
• Cross-platform (Windows, macOS, Linux)
REQUIREMENTS
• Neovim >= 0.10.0 (vim.uv is used unguarded)
• lib.nvim (required: notify, fs.json, net.curl, usercmd composer,
cross.executable)
• curl (for API requests)
• GitHub Personal Access Token with repo permission
Optional:
• pdfport.nvim for .pdf export • nvzone/menu for the dashboard's right-click menu
Create token:
1. https://github.com/settings/tokens
2. "Generate new token (classic)"
3. Select repo scope
4. Save token securely
INSTALLATION
When to use which:
Default (lazy) Minimal startup impact, loads on first command use
lazy = false Loads immediately at startup
event = "VimEnter" Loads after UI init (RECOMMENDED - matches the
plugin's own auto-fetch/dashboard auto-open timing)
With lazy.nvim (recommended):
{
"StefanBartl/github_stats.nvim",
dependencies = { "StefanBartl/lib.nvim" },
event = "VimEnter",
config = function()
require("github_stats").setup({
repos = { "user/repo1", "user/repo2" },
})
end,
}
With lazy.nvim (eager):
{
"StefanBartl/github_stats.nvim",
dependencies = { "StefanBartl/lib.nvim" },
lazy = false,
config = function()
require("github_stats").setup({
repos = { "user/repo1", "user/repo2" },
})
end,
}
With packer.nvim:
use {
"StefanBartl/github_stats.nvim",
requires = { "StefanBartl/lib.nvim" },
config = function()
require("github_stats").setup({
repos = { "user/repo1", "user/repo2" },
})
end,
}
CONFIGURATION
Two configuration methods are supported:
Option A: Direct Setup (Recommended)
require("github_stats").setup({
repos = { "username/repo1", "username/repo2" },
token_source = "env", -- or "file"
token_env_var = "GITHUB_TOKEN",
fetch_interval_hours = 24,
notification_level = "all", -- "all", "errors", "silent"
progress_style = "auto", -- indicator while a manual fetch runs
})
Option B: Config File
Create: ~/.config/nvim/lua/plugins/github-stats/config.json
{
"repos": ["username/repo1", "username/repo2"],
"token_source": "env",
"token_env_var": "GITHUB_TOKEN",
"fetch_interval_hours": 24,
"notification_level": "all"
}
Then in init.lua:
require("github_stats").setup() -- Reads from config.json
Why Option B?
If you sync Neovim config across systems via Git, using config.json in
stdpath('config') allows:
• Same historical data across all systems
• Consistent repository lists
• Everything in one backup-able location
See docs/configurations/INTRO.md for detailed guide.
Configuration Options:
repos List of individually tracked repositories
(format: "owner/repo")
Type: string[]
Required: only if watch_users is not set
watch_users GitHub usernames whose public repos are
auto-discovered and tracked in addition to repos
Type: string[]
Default: {}
background.enabled Master switch for the silent background
fetch/discovery cycle. When false, only manual
:GithubStats fetch ever fetches data.
Type: boolean
Default: true
token_source Token source ("env" or "file")
Type: string
Default: "env"
token_env_var Environment variable name
Type: string
Default: "GITHUB_TOKEN"
token_file Path to token file (when token_source="file")
Type: string
Example: "~/.github_token"
fetch_interval_hours Hours between automatic fetches
Type: number
Default: 24
notification_level Notification verbosity
Type: "all" | "errors" | "silent"
Default: "all"
progress_style Progress indicator while a fetch is in flight.
Type: "auto" | "notify" | "statusline" | "fidget"
| "float" | "kit"
Default: "auto"
A full fetch is four API calls per repository, so
over a dozen-plus repositories it can run for a
while with no output of its own.
Requires lib.nvim, which provides the indicator
via its lib.nvim.progress module. Without
lib.nvim installed this option is silently a
no-op and fetching behaves exactly as before.
Only manual fetches (:GithubStats fetch, the
dashboard refresh keys) show an indicator — the
silent background cycle never does, matching how
it already suppresses info notifications.
"auto" prefers fidget.nvim when installed and
falls back to vim.notify. "statusline" draws
nothing and instead publishes the text for your
own statusline to read via
`require("lib.nvim.progress.styles.statusline")
.active()`.
config_dir Custom config directory (advanced)
Type: string
Default: stdpath('config') .. '/lua/plugins/github-stats'
data_dir Custom data directory (advanced)
Type: string
Default: config_dir .. '/data'
digest_dir Where the per-repository digest for other programs
is written (see |github_stats-digest|). Local to one
machine on purpose: set it in setup(), not in the
synced config.json.
Type: string
Default: stdpath('data') .. '/github_stats.nvim'
digest_daily_days Days of daily values each digest keeps per metric.
Type: integer
Default: 400
Custom Storage Paths Example:
require("github_stats").setup({
repos = { "username/repo" },
config_dir = "~/my-github-stats",
data_dir = "/mnt/nas/github-data",
})
Background Fetching & Auto-Discovered Repos:
By default a silent background cycle starts on VimEnter and stays active for the whole session (not just a one-shot startup check). It periodically checks whether a fetch is due (per fetch_interval_hours) and fetches quietly: no notification on success. Real failures (bad token, network, rate limit) still notify, subject to notification_level. Track every public repo of a GitHub user with one line:
require("github_stats").setup({
repos = { "username/some-other-repo" }, -- still tracked individually
watch_users = { "username" }, -- all public repos of this user
})
watch_users is re-resolved every background cycle (new repos show up automatically) and is additive to repos. Repos you lack push access to simply never populate data, silently. Disable background fetching entirely:
require("github_stats").setup({
repos = { "username/repo" },
background = { enabled = false }, -- only manual :GithubStats fetch runs
})
Token Setup:
Environment Variable:
# In ~/.bashrc, ~/.zshrc, etc.
export GITHUB_TOKEN="ghp_your_token_here"
Token File:
echo "ghp_your_token_here" > ~/.github_token
chmod 600 ~/.github_token
Then in setup:
require("github_stats").setup({
repos = { "username/repo" },
token_source = "file",
token_file = "~/.github_token",
})
COMMANDS
One command, :GithubStats <subcommand> (built via lib.nvim.bindings.usercmd.composer, with <Tab> completion at every level -- subcommand name, then each positional argument; repo names and date presets complete dynamically from live config). Bang attaches to the VERB, not the subcommand: :GithubStats! dashboard (not :GithubStats dashboard!). See |github_stats-dashboard| for the dashboard subcommand. *:GithubStats* *:GithubStats-fetch* :GithubStats fetch [force] Fetches statistics for all configured repositories. Optional:force- Ignores interval and forces immediate fetch Autocompletion:force
Examples:
:GithubStats fetch " Respects 24h interval
:GithubStats fetch force " Immediate fetch
*:GithubStats-show*
:GithubStats show {repo} {metric} [start_date] [end_date]
Shows detailed statistics for a repository and metric.
Parameters:
{repo} Repository in "owner/repo" format
{metric} "clones" or "views"
[start_date] Optional start (ISO: YYYY-MM-DD)
[end_date] Optional end (ISO: YYYY-MM-DD, default: today)
Smart Defaults:
No start_date → All available data
No end_date → Today's date
Plugin notifies about applied defaults
Autocompletion: Repository names, metrics
Examples:
:GithubStats show username/repo clones
:GithubStats show username/repo views 2025-01-01
:GithubStats show username/repo clones 2025-01-01 2025-12-31
*:GithubStats-summary*
:GithubStats summary {metric}
Shows summary across all configured repositories.
Parameters:
{metric} "clones" or "views"
Note: Does not accept date parameters (shows entire history)
Autocompletion: Metrics
Examples:
:GithubStats summary clones
:GithubStats summary views
*:GithubStats-referrers*
:GithubStats referrers {repo} [limit]
Shows top referrers for a repository.
Parameters:
{repo} Repository in "owner/repo" format
{limit} Number of results (default: 10)
Autocompletion: Repository names
Examples:
:GithubStats referrers username/repo
:GithubStats referrers username/repo 20
*:GithubStats-paths*
:GithubStats paths {repo} [limit]
Shows most visited paths in a repository.
Parameters:
{repo} Repository in "owner/repo" format
{limit} Number of results (default: 10)
Autocompletion: Repository names
Examples:
:GithubStats paths username/repo
:GithubStats paths username/repo 20
*:GithubStats-chart*
:GithubStats chart {repo} {metric} [start_date] [end_date]
Displays ASCII sparkline chart for traffic data.
Parameters:
{repo} Repository in "owner/repo" format
{metric} "clones", "views", or "both" (comparison)
[start_date] Optional start (ISO: YYYY-MM-DD)
[end_date] Optional end (ISO: YYYY-MM-DD, default: today)
Smart Defaults:
No start_date → All available data
No end_date → Today's date
Autocompletion: Repository names, metrics
Examples:
:GithubStats chart username/repo clones
:GithubStats chart username/repo both
:GithubStats chart username/repo views 2025-01-01 2025-12-31
*:GithubStats-export*
:GithubStats export {repo|all} {metric} {filepath}
Exports data to CSV, Markdown, or PDF format.
Parameters:
{repo|all} Repository or "all" for summary
{metric} "clones", "views", or "both" (combined report)
{filepath} Output file (.csv, .md, or .pdf extension)
Format Support:
CSV: Single repository only
Markdown: Single repository or all repositories
PDF: Single repository or all repositories (same content as
Markdown; requires pdfport.nvim, see below)
PDF export (optional dependency):
Routes through pdfport.nvim (github.com/StefanBartl/pdfport.nvim,
soft dependency, pcall-guarded): the exact same report
export_markdown()/export_summary_markdown()/etc. would write to a
.md file is instead handed to pdfport.create() as text (no
intermediate .md file). Needs pdfport.nvim installed and a
markdown producer available (pandoc + a PDF engine) --
pdfport.can_create("markdown"). Without pdfport.nvim installed,
or without an available producer, the export fails with a clear
error instead of silently falling back to another format.
Extension Defaulting:
If {filepath} has no extension at all, one is appended
automatically: ".md" for the "all" target (the only format it
supports by default), ".csv" otherwise. A path that already has
some other extension (e.g. ".txt") is left alone and still
errors, since silently rewriting a deliberately-named path would
be more surprising than helpful.
Parent Directories:
Created automatically if they don't exist yet.
Combined Metric ("both"):
Merges clones and views into a single report instead of two
separate ones -- one CSV row (or Markdown table row) per date,
with both metrics side by side. "all" + "both" produces one
combined Markdown/PDF summary across every configured repository.
Summary reports (target "all", or a single-repo report) also
include a "## Highlights" section: most cloned/viewed repository,
best month, and best single day.
Autocompletion: Repository names, metrics, file paths
Examples:
:GithubStats export username/repo clones ~/data.csv
:GithubStats export username/repo views ~/report.md
:GithubStats export username/repo clones ~/report.pdf
:GithubStats export username/repo both ~/combined.csv
:GithubStats export all clones ~/summary.md
:GithubStats export all clones ~/summary.pdf
:GithubStats export all both ~/summary.md
:GithubStats export username/repo clones ~/reports/repo " -> ~/reports/repo.csv
*:GithubStats-diff*
:GithubStats diff {repo} {metric} {period1} {period2}
Compares metrics between two time periods.
Parameters:
{repo} Repository in "owner/repo" format
{metric} "clones" or "views"
{period1} First period (YYYY-MM or YYYY)
{period2} Second period (YYYY-MM or YYYY)
Period Formats:
YYYY-MM: Single month (e.g., 2025-01)
YYYY: Full year (e.g., 2025)
Autocompletion: Repository names, metrics, period suggestions
Examples:
:GithubStats diff username/repo clones 2025-01 2025-02
:GithubStats diff username/repo views 2024 2025
*:GithubStats-compact*
:GithubStats compact [dry-run]
Archives old clones/views data and prunes stale referrers/paths
snapshots. Runs automatically at most once per 24h after a fetch;
this command runs it on demand.
Optional: dry-run - Reports would-be archived/deleted counts and
freed bytes without touching disk
Autocompletion: dry-run
Governed by: retention.enabled, retention.cutoff_days (floor 14),
retention.prune_days
Examples:
:GithubStats compact dry-run
:GithubStats compact
*:GithubStats-digest*
:GithubStats digest
Rebuilds, now, the per-repository digest that other programs read
(see |github_stats-digest|). It is written automatically after every
fetch and whenever the synced history is newer than it; use this after
a sync from your other machine. Reports how many digests were written,
were unchanged, or had no history, and where they went.
Examples:
:GithubStats digest
*:GithubStats-debug*
:GithubStats debug
Shows debug information and tests API connectivity.
Information Shown:
• Configuration status
• Token availability
• Last fetch summary with detailed errors
• Test API call for first repository
Use when:
• After initial setup
• Troubleshooting errors
• Before opening support issues
*:checkhealth-github_stats*
:checkhealth github_stats
Performs comprehensive diagnostics.
Checks:
• Configuration validity
• Token access
• curl availability (cross-platform)
• Storage paths
• Digest: options, writable directory, root.json, lag behind the history
• API connectivity (with timeout)
DASHBOARD
The dashboard provides an interactive TUI for monitoring multiple repositories simultaneously with real-time statistics and visual trend indicators.
Opening the Dashboard:
*:GithubStats-dashboard*
:GithubStats dashboard Open dashboard
:GithubStats! dashboard Open with forced refresh
Dashboard Layout:
┌─────────────────────────────────────────────────────────┐
│ GitHub Stats Dashboard [?] Help [q] Quit │
├─────────────────────────────────────────────────────────┤
│ Overall: 3 repos | 12,345 clones | 5,678 views │
│ Last update: 2025-12-23 10:30:00 │
├─────────────────────────────────────────────────────────┤
│ ▶ username/repo1 ⬆ +15% ↗ │
│ Clones: 1,234 | Views: 5,678 | Referrers: 45 │
│ ▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁ │
└─────────────────────────────────────────────────────────┘
Key Bindings:
Navigation:
j, <Down> Navigate to next repository k, <Up> Navigate to previous repository <Enter> Show detailed statistics for selected repository
Actions:
r Drop the in-memory read cache and re-render from disk
(no API call); see |github_stats-dashboard-caching|
R Force-fetch all repositories from GitHub (bypass interval)
f Force-fetch selected repository from GitHub (bypass interval)
s Cycle sort criteria (clones/views/name/trend)
t Cycle time range (7d/30d/90d/max)
T Enter a custom time range (e.g. "14d", "3m", "1y",
"since:2025-01-01", or any date_presets name)
m Set the maximum time range: the longest duration the
stored data covers, notifying the resolved span
Other:
? Toggle help overlay q, <Esc> Quit dashboard
Configuration:
dashboard.enabled (boolean)
Enable dashboard functionality.
Default: true
dashboard.auto_open (boolean)
Automatically open dashboard on VimEnter.
Default: false
dashboard.refresh_interval_seconds (integer)
While the dashboard is open, re-render it every N seconds. This
re-renders only; it never fetches, so an open dashboard cannot burn
the API rate limit (see |github_stats-dashboard-auto-refresh|).
Set to 0 to disable. A non-number value is ignored.
Default: 300 (5 minutes)
dashboard.trend_window_days (integer)
Days per trend comparison window: each entry's arrow compares the last
N complete days against the N before them, the same N whatever range is
displayed. See |github_stats-dashboard-trend|.
Default: 7
dashboard.sort_by (string)
Default sort criteria: "clones", "views", "name", or "trend".
Default: "clones"
dashboard.time_range (string)
Default time range: "7d", "30d", "90d", "max"/"all", or any expression
accepted by the 'T' custom time range prompt (e.g. "14d", "3m", "1y",
"since:2025-01-01", a date_presets name). See
|github_stats-time-range-expressions|.
Default: "30d"
dashboard.keybindings (table)
Customize keybindings. See |github_stats-dashboard-keybindings|
Example Configuration:
require("github_stats").setup({
repos = { "user/repo1", "user/repo2" },
dashboard = {
enabled = true,
auto_open = false,
refresh_interval_seconds = 300,
sort_by = "clones",
time_range = "30d",
trend_window_days = 7,
keybindings = {
navigate_down = "j",
navigate_up = "k",
show_details = "<CR>",
refresh_selected = "r",
refresh_all = "R",
force_refresh = "f",
cycle_sort = "s",
cycle_time_range = "t",
custom_time_range = "T",
max_time_range = "m",
show_help = "?",
quit = "q",
},
},
})
Features:
Repository Cards:
Each repository is displayed with: • Current statistics (clones, views, referrers) • Trend indicator (↑↓ with percentage change) • Mini sparkline showing recent activity • Selection indicator (▶) for current repository
Overall Summary:
Displays aggregate statistics: • Total number of configured repositories • Combined clones and views across all repos • Timestamp of last data refresh
Visual Indicators:
⬆ +15% Growing (positive trend)
⬇ -8% Declining (negative trend)
⬌ 0% Stable (no change)
⬌ n/a Neither comparison window holds any data
*github_stats-dashboard-caching*
Caching:
Stored metrics are read from disk once and kept in memory until something
changes them. Without that, every keypress re-read and re-decoded the
entire stored history of every configured repository.
The cache is dropped when a fetch writes, when a retention run archives
or prunes, and when 'r' is pressed. There is no time-based expiry, so
nothing serves stale data because a timer had not fired yet, and nothing
re-reads because one did.
'r' is therefore how to pick up a change this Neovim did not make
itself -- a fetch from another window, or another Neovim entirely.
*github_stats-dashboard-highlights*
Colours:
The dashboard defines named highlight groups and links them to stock
groups with default = true, so colours follow the user's colourscheme
and a single :hi link overrides them permanently:
GithubStatsHeader -> Title
GithubStatsTotals -> MoreMsg
GithubStatsStatus -> Comment
GithubStatsKeyHint -> Comment
GithubStatsRepo -> Identifier
GithubStatsSelected -> PmenuSel
GithubStatsLabel -> Comment
GithubStatsValue -> Number
GithubStatsSparkline -> Special
GithubStatsTrendUp -> DiagnosticOk
GithubStatsTrendDown -> DiagnosticError
GithubStatsTrendFlat -> Comment
GithubStatsSeparator -> NonText
Example override:
vim.api.nvim_set_hl(0, "GithubStatsTrendUp", { link = "DiffAdd" })
Totals:
The header's second line summarises every configured repository over the active range, and names the one with the most clones:
2 repos 2,540 clones 2,540 views top:user/a
It follows the time range, so "max" gives all-time totals. No top repository is named while nothing has been cloned.
Sparklines:
Each entry's "Period:" line ends with a sparkline of daily clones over the active range, sampled to a fixed width so a longer range compresses rather than widening the entry:
Period: 2026-08-05 to 2026-08-24 ▆▂▆▂▅▁▅▁▄█▃▇▃▆▂▆▂▅▁▅
A repository with no data in range shows no sparkline.
*github_stats-dashboard-trend*
Trend:
The arrow compares the lastdashboard.trend_window_dayscomplete days (default 7) against the same number of days before them. The comparison is fixed: it does not follow the displayed range, so "+40%" means the same thing at Range:7d and at Range:max, and sorting by trend orders numbers that are actually comparable. The window is named in the header as "Trend:7d/7d". It is measured back from yesterday, not today: today's data is always incomplete and is excluded from every aggregation, so anchoring on today would compare six days against seven and invent a decline. "n/a" means neither window holds data -- a different statement from "0%", which means genuinely flat. Such entries sort below all others undersort_by = "trend".
Sorting:
Press 's' to cycle through sort options: • clones - Sort by total clone count • views - Sort by total view count • name - Sort alphabetically • trend - Sort by percentage change
Time Ranges:
Press 't' to cycle through the fixed quick-access ranges: • 7d - Last 7 days • 30d - Last 30 days • 90d - Last 90 days • max - The maximum duration the stored data covers Press 'm' to jump straight to "max" without stepping through the cycle. It also notifies the window it resolved to, e.g. "max (2025-03-04 to 2026-08-22, 172 days)". Press 'T' to type an arbitrary range instead, via a prompt pre-filled with the current value. See |github_stats-time-range-expressions| for every accepted form. Whatever range is active, the header status line appends the window it actually resolved to, or "(no data)" when nothing is stored yet:
Sort:clones Range:max (2025-03-04 -> 2026-08-22, 172 days)
*github_stats-time-range-expressions*
Time Range Expressions:
Both the 'T' dashboard prompt and any time_range field accept:
• "all" / "max" - no filtering, every available day; identical
in effect, "max" is the label the 't' cycle
and the 'm' key produce
• "Nd" / "Nw" - N days / N weeks back from today
• "Nm" / "Ny" - N calendar months / years back; the day is
clamped to the target month's length, so one
month back from the 31st lands on the 28th,
29th or 30th rather than rolling forward
• "since:YYYY-MM-DD" - that date through today
• "YYYY-MM-DD" - same as "since:YYYY-MM-DD"
• any |github_stats-date-presets| name, built-in or user-custom
(e.g. "this_month", "this_year", "last_quarter", a custom preset)
Examples: "14d", "3m", "1y", "since:2025-01-01", "this_year".
An unrecognized expression is rejected with an error notification and
the previous range is kept.
*github_stats-dashboard-auto-refresh*
Auto-Refresh:
While the dashboard is open it re-renders itself everydashboard.refresh_interval_secondsseconds (default 300, 0 disables). It re-renders; it never fetches. The GitHub traffic API is a rolling 14-day window updated once a day, so polling it every few minutes would spend rate limit on data that cannot have changed. Fetching stays the job of 'R', 'f', and thefetch_interval_hoursgate. What the periodic re-render does pick up is anything that reached disk meanwhile: a background fetch, a:GithubStats fetchfrom another window, or a retention run. The timer is stopped and closed when the dashboard closes, on the same teardown path as everything else.
See also:
|github_stats-commands| |github_stats-configuration|
VISUALIZATION
The visualization module provides ASCII charts and sparklines for traffic data visualization directly in Neovim.
Sparklines:
Unicode block characters (▁▂▃▄▅▆▇█) represent data points. Normalized to show relative values across the range.
Bar Charts:
Horizontal bars with numeric labels. Useful for comparing multiple items (e.g., referrers, paths).
Comparison Charts:
Side-by-side sparklines for count vs uniques. Shows both metrics in a single view.
Example Output:
GitHub Stats: username/repo/clones
────────────────────────────────────────────────────────────────
▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁▂▃▅▇█▇▅▃▂▁
Period: 2025-11-20 to 2025-12-20 (30 days)
Max: 1,234 | Avg: 567 | Min: 123 | Total: 17,010
EXPORT
The export module supports CSV and Markdown formats for external analysis and documentation.
CSV Format:
repository,metric,date,count,uniques
username/repo,clones,2025-12-20,45,12
username/repo,clones,2025-12-21,52,15
Markdown Format:
- Formatted tables with daily breakdown - Summary statistics (max, avg, min, total) - Recent values highlighted - Suitable for documentation and reports
Summary Export:
When using "all" as repository target with Markdown format, creates a comprehensive report across all configured repositories with: - Overview table - Detailed sections per repository - Recent data trends
DIFF
The diff module provides period-over-period comparison to analyze trends and growth.
Period Formats:
YYYY-MM Monthly comparison (e.g., 2025-01) YYYY Yearly comparison (e.g., 2025)
Calculated Metrics:
- Total count and uniques per period - Number of days with data - Average per day - Percentage change
Example Output:
Period Comparison: username/repo - clones
══════════════════════════════════════════════════════════════════
Period 1: 2025-01
Total Count: 1,234
Total Uniques: 567
Days: 31
Avg/Day: 39 count, 18 uniques
Period 2: 2025-02
Total Count: 1,423
Total Uniques: 645
Days: 28
Avg/Day: 50 count, 23 uniques
Changes:
──────────────────────────────────────────────────────────────────
Count: +15.3%
Uniques: +13.8%
DATE PRESETS
Date range presets provide quick access to common time periods without requiring full ISO date strings.
Built-in Presets:
today Current day only yesterday Previous day last_week 7 days ago to today last_month 30 days ago to today last_quarter 90 days ago to today last_year 365 days ago to today this_week Monday to today this_month First of month to today this_quarter Start of quarter to today this_year January 1st to today
Where presets resolve:
Completion offers preset names in more slots than the code resolves them
in. Resolution happens only in analytics.parse_time_range, and only these
reach it:
Dashboard T prompt always
:GithubStats chart, arg 3 only if the name contains "last" or looks
like Nd (chart.lua routes those to
time_range)
:GithubStats show never - the argument becomes start_date,
which accepts YYYY-MM-DD only
:GithubStats diff never - parse_period accepts YYYY-MM or
YYYY only
Where a name is not resolved it fails to parse as a date and becomes NO
filter, silently reporting the full stored history.
Usage with Commands:
:GithubStats chart username/repo clones last_month
:GithubStats show username/repo clones 2025-12-01 " ISO dates here
Custom Presets:
Custom presets are Lua functions that return two ISO date strings. They must be added programmatically after setup().
Example Configuration:
-- In init.lua
require("github_stats").setup()
local config = require("github_stats.config").get()
-- Fiscal Year Preset (April 1 - March 31)
config.date_presets.custom.fiscal_year = function()
local now = os.date("*t")
local fy_year = now.month >= 4 and now.year or now.year - 1
local start_date = string.format("%04d-04-01", fy_year)
local end_date = string.format("%04d-03-31", fy_year + 1)
return start_date, end_date
end
-- Current Sprint (2-week periods)
config.date_presets.custom.current_sprint = function()
local now = os.time()
local sprint_length = 14 * 86400
local date_info = os.date("*t", now)
local days_since_monday = (date_info.wday == 1) and 6 or (date_info.wday - 2)
local monday = now - (days_since_monday * 86400)
local sprint_start = monday - (monday % sprint_length)
local sprint_end = sprint_start + sprint_length - 86400
return os.date("%Y-%m-%d", sprint_start), os.date("%Y-%m-%d", sprint_end)
end
Using Custom Presets:
:GithubStats chart username/repo clones last_fiscal_year
" fiscal_year / current_sprint resolve only at the dashboard T prompt
Configuration Options:
date_presets.enabled (boolean)
Whether date presets are enabled.
Default: true
date_presets.builtins (string[])
List of enabled built-in preset names.
Default: All built-in presets enabled
date_presets.custom (table<string, function>)
User-defined preset functions.
Default: {}
Preset Function Requirements:
• Must return two strings in ISO format (YYYY-MM-DD) • Start date must be before or equal to end date • Should handle edge cases (month/year boundaries) • Can use os.date(), os.time() for calculations
Autocompletion:
Tab-completion shows all available presets when entering date parameters, including in the slots that do not resolve them (see above) in commands that support date ranges: • |:GithubStats-show| • |:GithubStats-chart| • |:GithubStats-diff|
Example:
:GithubStats show username/repo clones <Tab>
" Shows: today, yesterday, last_week, ..., fiscal_year, current_sprint
Troubleshooting:
Preset not appearing:
• Restart Neovim after adding custom preset
• Verify preset added after setup() call
• Check: :lua print(vim.inspect(require("github_stats.date_presets").list()))
"Unknown preset" error:
• Check spelling of preset name • Ensure config.date_presets.enabled = true • Verify preset function is properly assigned
Wrong date calculation:
• Use os.date("*t") for reliable date components
• Account for timezone/DST if relevant
• Test with different dates (month/year boundaries)
See also:
|github_stats-commands| |github_stats-configuration| For detailed examples and best practices: docs/configurations/USER-DEFINED-DATE-PRESETS.md
API
Lua API for advanced usage:
Setup:
require("github_stats").setup({
repos = { "username/repo" },
-- Options (see |github_stats-configuration|)
})
Modules:
local config = require("github_stats.config")
local api = require("github_stats.api")
local storage = require("github_stats.storage")
local fetcher = require("github_stats.fetcher")
local analytics = require("github_stats.analytics")
local visualization = require("github_stats.visualization")
local export = require("github_stats.export")
local diff = require("github_stats.diff")
Example - Programmatic Fetch:
local fetcher = require("github_stats.fetcher")
fetcher.fetch_all(true, function(summary)
print("Success:", #summary.success)
print("Errors:", vim.tbl_count(summary.errors))
end)
Example - Data Query:
local analytics = require("github_stats.analytics")
local stats, err = analytics.query_metric({
repo = "username/repo",
metric = "clones",
start_date = "2025-01-01",
end_date = "2025-12-31",
})
if stats then
print("Total clones:", stats.total_count)
end
Example - Visualization:
local visualization = require("github_stats.visualization")
local sparkline = visualization.generate_sparkline({
10, 15, 20, 25, 30, 25, 20, 15, 10
})
print(sparkline) -- ▂▃▅▆█▆▅▃▂
STORAGE
Data Structure:
~/.config/nvim/lua/plugins/github-stats/
├── config.json # User configuration (Option B)
├── last_fetch.json # Interval tracking
└── data/
└── username_repo/
├── clones/
│ ├── 2025-12-20T10-30-00.json
│ └── 2025-12-21T10-30-00.json
├── views/
├── referrers/
└── paths/
Custom Paths:
Default paths can be customized in setup():
require("github_stats").setup({
repos = { "username/repo" },
config_dir = "~/my-github-stats",
data_dir = "/mnt/nas/github-data",
})
File Format (Example):
{
"timestamp": "2025-12-20T10:30:00Z",
"data": {
"count": 42,
"uniques": 15,
"clones": [
{
"timestamp": "2025-12-19T00:00:00Z",
"count": 10,
"uniques": 5
}
]
}
}
Storage Management:
• JSON-based (~2KB per data point) • Atomic writes (temp + rename) • Automatic directory creation • No automatic cleanup (manual when needed)
DIGEST
Other programs (a desktop app, another Neovim plugin) can show this traffic without a token and without re-implementing the plugin's rules: after each fetch the plugin writes one small JSON file per repository, plus a pointer. The file contract is docs/FEATURES/DIGEST.md.
Where:
<stdpath('data')>/github_stats.nvim/root.json pointer, always here
<digest_dir>/digest/<owner_repo>.json one file per repository
The history lives in the (synced) Neovim config; the digest is derived and rewritten in place, so it is local to each machine and rebuilt from the history whenever that is newer.root.jsonnames where the digests really are, so a reader needs no setting even whendigest_diris overridden.
Contents:
• 7, 30 and 90 complete days of views and clones (count and uniques) and a
7-day trend in percent
• the daily series, oldest first, at most digest_daily_days days
• GitHub's top 10 referrers and paths (absent = unknown, not "none")
• generated (when built) and fetched (the data it was built from)
Never contained: the token, any path of yours.
Reading it from Lua:
-- Probe this module, not require("github_stats"): the top-level module
-- loads the dashboard, which needs ui.nvim.
local dir = require("github_stats.digest").digest_dir()
Commands: |:GithubStats-digest|
TROUBLESHOOTING
Problem: "Token error: GITHUB_TOKEN not set"
Solution:
1. Set token: export GITHUB_TOKEN="ghp_..."
2. Restart Neovim
3. Or configure token_file in setup
Problem: "No data found for username/repo"
Possible Causes:
1. Repository name in configuration incorrect (case-sensitive)
2. No data fetched yet → :GithubStats fetch force
3. Token lacks permission for repository
Problem: "Fetched X metrics, Y errors"
Solution:
Run :GithubStats debug to see detailed error information for each
failed repository/metric combination.
Common error codes:
• 404 Not Found - Repository name incorrect or deleted
• 403 Forbidden - Token lacks permissions or rate limit
• 401 Unauthorized - Invalid or expired token
Problem: "curl not found in PATH"
Solution:
• Linux/Debian/Ubuntu: sudo apt install curl
• macOS: brew install curl
• Windows 10+: curl is included since build 1803
• Windows (manual): https://curl.se/windows/
Problem: Autocompletion not working
Solution:
1. Check Neovim version: >= 0.10.0 required
2. Run: :checkhealth github_stats
3. Verify configuration is loaded
Diagnostic Workflow:
1. :checkhealth github_stats
2. :GithubStats debug
3. :messages
4. Check config: ~/.config/nvim/lua/plugins/github-stats/config.json
For comprehensive troubleshooting guide, see:
docs/troubleshooting.md