GitHub Actions
The repository ships a composite Action, so a workflow needs no Go toolchain: it downloads a release binary and calls it.
- uses: jmrplens/ghchronicle@v1 with: token: ${{ secrets.GHCHRONICLE_TOKEN }} mode: once config: .github/ghchronicle.yamlInputs
Section titled “Inputs”| Input | Default | What it does |
|---|---|---|
token | required | A personal access token. The automatic GITHUB_TOKEN is not enough |
config | "" | Path to a configuration file. Omit to run with defaults built from user |
user | the repository owner | The account to collect when no config file is given |
mode | once | once, backfill or card |
backfill- | "" | Bound for backfill: a date, 90d, 2y or a Go duration. Empty means no bound |
card | "" | Path of the SVG to write. Empty means no card |
card- | summary | One of the thirteen registered layouts |
card- | auto | dark, light, auto, or both for a light card and its _dark twin |
card- | "" | Comma-separated fields. Empty means the layout’s default |
card- | once | once, loop or off; loop changes only terminal and ticker |
card- | "" | Card width in pixels. Empty draws the layout at its own width; each one draws between two ends of its own, stated in its section. Only activity-heatmap spends the room on data, a whole year of the calendar at its far end |
card- | "" | How fast an animated layout plays, as a decimal from 0 to 1. Empty means 0.5, the pace every card has always been drawn at; below it the card is slower, above it faster, and every animated layout scales together. 0 is the slowest animation and not a still card, card-motion: off is |
include- | false | true counts private repositories when no config file is given (the default up to v1.0.0). See the warning below |
version | latest | The release to install |
The three modes
Section titled “The three modes”One sweep against a configuration file you supply, which is how a workflow feeds a database.
name: Collecton: schedule: - cron: "*/30 * * * *" workflow_dispatch:
jobs: collect: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: jmrplens/ghchronicle@v1 with: token: ${{ secrets.GHCHRONICLE_TOKEN }} mode: once config: .github/ghchronicle.yaml env: INFLUX_TOKEN: ${{ secrets.INFLUX_TOKEN }}The config file refers to the database credentials as ${VAR}, so they go
in as secrets and never into the repository.
One sweep and one SVG, and nothing written to any database. This is the only mode that needs no store configured at all.
name: Profile cardon: schedule: - cron: "17 6 * * *" workflow_dispatch:
permissions: contents: write
jobs: card: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: jmrplens/ghchronicle@v1 with: token: ${{ secrets.GHCHRONICLE_TOKEN }} mode: card card: generated/github-stats.svg card-layout: github-stats card-theme: both - name: Commit if it changed run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add generated/ git diff --staged --quiet || git commit -m "Update the profile card" git pushThe SVG is byte-identical for the same input, so a day with no change produces no commit.
Reaches as far back as GitHub allows and waits for the rate limit rather than stopping. Run it once, by hand.
name: Backfillon: workflow_dispatch: inputs: since: description: "A date, 90d, 2y, or empty for no bound" default: "2y"
jobs: backfill: runs-on: ubuntu-latest timeout-minutes: 360 steps: - uses: actions/checkout@v7 - uses: jmrplens/ghchronicle@v1 with: token: ${{ secrets.GHCHRONICLE_TOKEN }} mode: backfill backfill-since: ${{ inputs.since }} config: .github/ghchronicle.yamlGive it a generous timeout-minutes: an unbounded backfill parks at every
rate limit reset, and a job that is killed half way has spent the quota and
kept part of the benefit.
A card in your profile README
Section titled “A card in your profile README”GitHub shows the README of the repository named after your account, <you>/<you>,
at the top of your profile. The card lives in that repository as a file, the
README points at it once, and a scheduled workflow replaces the file. The README
itself is never rewritten.
-
Create a personal access token (see the token) and store it in
<you>/<you>as the secretGHCHRONICLE_TOKEN. The automaticGITHUB_TOKENwill not do, even for public numbers: it is not a user, so the Action cannot list your repositories with it and the card comes out empty. -
Add
.github/workflows/card.yml:name: Profile cardon:schedule:- cron: "17 6 * * *"workflow_dispatch:permissions:contents: write# One run at a time: two that overlap would race each other to push.concurrency:group: profile-cardcancel-in-progress: falsejobs:card:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v7- uses: jmrplens/ghchronicle@v1with:token: ${{ secrets.GHCHRONICLE_TOKEN }}mode: cardcard: generated/card.svgcard-layout: animated-counterscard-theme: bothcard-motion: once- name: Commit if it changedrun: |git config user.name "github-actions[bot]"git config user.email "41898282+github-actions[bot]@users.noreply.github.com"git add generated/git diff --staged --quiet || git commit -m "Update the profile card"git push -
Paste this line into
README.mdwherever the card should appear, once:<picture><source media="(prefers-color-scheme: dark)" srcset="generated/card_dark.svg"><img src="generated/card.svg" alt="My GitHub statistics"></picture> -
Run the workflow by hand from the Actions tab the first time, so the files exist before the first scheduled run.
More than one card. Repeat the uses: jmrplens/ghchronicle@v1 step with
another card path and layout, and paste one <picture> per card. Each step is
a sweep of its own, and each draws every number again: a run that writes a card
collects every family whatever its cadence says, and one in card mode writes
nothing back to the state file. Several card steps can therefore share one
config, and so one state_file, and each card is the whole account. That is
also what each one costs: a card is a cold sweep of every family, so N cards
are N sweeps of the per-family price.
A README that is not at the root. This is for other repositories, since
GitHub shows a profile’s README only from the root of <you>/<you>. The paths
in the <picture> are relative to the README, so a docs/README.md points at
../generated/card.svg.
Two things a hosted runner does not keep
Section titled “Two things a hosted runner does not keep”The state file does not survive between runs. Each run therefore does the full stargazer walk again. On a small account that is a handful of calls; on an account with many stars, cache it:
- uses: actions/cache@v4 with: path: ~/.ghchronicle key: ghchronicle-state-${{ github.run_id }} restore-keys: ghchronicle-state-and point state_file at ~/.ghchronicle/state.json in the config. A card
mode run reads a restored state file, which is what lets it skip the walk, and
never writes one back: it delivers its points to the card and to no store, so
nothing it learned may tell the next collection that a family is already done.
What fills the cache is a once or backfill step.
Without the Action
Section titled “Without the Action”The same thing by hand, if you would rather not depend on it:
- uses: actions/setup-go@v7 with: go-version: stable- run: go install github.com/jmrplens/ghchronicle/cmd/ghchronicle@latest- run: ghchronicle -config .github/ghchronicle.yaml -card profile.svg -card-only env: GITHUB_TOKEN: ${{ secrets.GHCHRONICLE_TOKEN }}