Skip to content

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.yaml
InputDefaultWhat it does
tokenrequiredA personal access token. The automatic GITHUB_TOKEN is not enough
config""Path to a configuration file. Omit to run with defaults built from user
userthe repository ownerThe account to collect when no config file is given
modeonceonce, backfill or card
backfill-since""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-layoutsummaryOne of the thirteen registered layouts
card-themeautodark, light, auto, or both for a light card and its _dark twin
card-fields""Comma-separated fields. Empty means the layout’s default
card-motiononceonce, loop or off; loop changes only terminal and ticker
card-width""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-speed""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-privatefalsetrue counts private repositories when no config file is given (the default up to v1.0.0). See the warning below
versionlatestThe release to install

One sweep against a configuration file you supply, which is how a workflow feeds a database.

name: Collect
on:
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.

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.

  1. Create a personal access token (see the token) and store it in <you>/<you> as the secret GHCHRONICLE_TOKEN. The automatic GITHUB_TOKEN will 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.

  2. Add .github/workflows/card.yml:

    name: Profile card
    on:
    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-card
    cancel-in-progress: false
    jobs:
    card:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v7
    - uses: jmrplens/ghchronicle@v1
    with:
    token: ${{ secrets.GHCHRONICLE_TOKEN }}
    mode: card
    card: generated/card.svg
    card-layout: animated-counters
    card-theme: both
    card-motion: once
    - 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 push
  3. Paste this line into README.md wherever 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>
  4. 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.

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.

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 }}