Skip to main content

termfetch

CI PyPI Python License: MIT

A neofetch-style profile card for your GitHub README — your photo as coloured terminal art on the left, live GitHub stats on the right, rendered to a single self-contained SVG.

example card

Why termfetch?

  • No image service at view time. The result is a static SVG committed to your repository.
  • Your design, not a preset badge. Use your own photo, fields, colours, crop, and character set.
  • Live without being fragile. A scheduled GitHub Action refreshes the card; README visitors never spend your API quota and never see an outage from a third-party renderer.
  • Portable. The output is one self-contained SVG that works in GitHub READMEs and anywhere else an image can be embedded.

Quickstart

python -m pip install termfetch
git clone https://github.com/saivarun1410/termfetch
cd termfetch

termfetch --config examples/config.json --out card.svg

Prefer an isolated CLI install? Use pipx install termfetch or uv tool install termfetch. To work from a checkout before installing, run python -m pip install -e ..

Then in your README:

![](card.svg)

--no-fetch skips the GitHub API entirely if you just want to iterate on the art.

Configuration

Everything lives in one JSON file.

{
  "github": "saivarun1410",
  "image": "assets/me.jpg",
  "imageCrop": [630, 0, 780, 780],
  "imageCols": 52,
  "charset": "blocks",
  "theme": "github-dark",
  "panelAlign": "middle",
  "sections": [
    {
      "title": "{{username}}@github",
      "fields": [
        ["Name", "{{name}}"],
        ["Member for", "{{uptime}}"],
        ["Editor", "Neovim"]
      ]
    }
  ]
}

Image

Key Default Notes
image Path to the source image, relative to the config file.
imageCrop whole image [x, y, width, height] in source pixels, applied before scaling.
imageCols 56 Width in characters. Row count follows from the aspect ratio.
charset blocks ascii, blocks, solid, or halfblocks. See below.
coloredImage true false renders in the theme's single foreground colour.
contrast / brightness / gamma 1.2 / 1.0 / 1.05 Applied before sampling.
colorStep 8 Colour quantisation. Higher merges more cells and shrinks the SVG.
invert false Flip the brightness-to-character mapping.

Choosing a charset.

  • halfblockssharpest. Every cell is , with the glyph painting the upper pixel and a background rect the lower one, so vertical resolution is doubled for the same card width. Use this if the art looks blocky; the cost is file size, since a photograph has few neighbouring cells that can merge.
  • blocks ( ░▒▓█) — keeps a visible terminal texture while letting colour carry the image. Much smaller output, noticeably coarser.
  • ascii ( .:-=+*#%@) — looks the most like real neofetch output, but it maps brightness to character density, so a backlit photo where the subject is darker than the background comes out as a hole. Good for logos and high-key images, risky for portraits.
  • solid — every cell . Clear, but reads as pixel art rather than terminal art.

File size. A halfblocks card is a few hundred KB; blocks is a few tens. Raising colorStep helps only slightly on photographs — the cost is the per-cell geometry, not repeated colours — so reducing imageCols is the effective lever if size matters.

Cropping. The card looks best framed on a face. Find the pixel coordinates however you like — this snippet prints a labelled grid over your image:

from PIL import Image, ImageDraw
im = Image.open("assets/me.jpg"); d = ImageDraw.Draw(im)
for x in range(0, im.width, 200):  d.line([(x,0),(x,im.height)], fill="cyan", width=4); d.text((x+6,6), str(x), fill="cyan")
for y in range(0, im.height, 200): d.line([(0,y),(im.width,y)], fill="magenta", width=4); d.text((6,y+6), str(y), fill="magenta")
im.show()

Layout and theme

Key Default Notes
theme github-dark github-dark, github-light, dracula, gruvbox, nord, tokyo-night.
colors Override individual theme colours: {"key": "#ff79c6"}.
windowTitle {{username}}@github Text in the title bar. Empty string to omit.
chrome true Draw the title bar and traffic-light buttons.
fontSize 13 Panel text size.
artFontSize matches fontSize Art cell size, set separately. This is the knob for "sharp but not huge". Detail comes from imageCols; if the art cell is tied to the panel text, adding columns is the same as enlarging the picture until it dwarfs the text. Drop this to 6–8 and raise imageCols instead.
keyWidth 13 Characters reserved for the key column. Widen if labels are truncated-looking.
panelAlign top middle centres the text against the art — better when you have few rows.
valueWrap 0 Wrap values longer than this many characters onto continuation rows aligned under the value column. 0 disables it. Without this, one long bio makes the card 1500px wide.

Data

Key Default Notes
github Username to fetch stats for. Omit to use only static text.
languageMode repos repos counts primary language per repo (1 API call). bytes sums bytes actually written (1 call per repo, more representative).
topLanguages 5

Available template variables: {{username}}, {{name}}, {{bio}}, {{location}}, {{company}}, {{blog}}, {{repos}}, {{stars}}, {{forks}}, {{followers}}, {{following}}, {{languages}}, {{created}}, {{uptime}}, {{today}}.

A field is dropped when its value resolves to nothing, or when it still references a variable there was no data for — so an unset bio leaves no dangling label, and --no-fetch renders a clean card rather than one covered in literal {{bio}} text. Fields can also be plain strings for a full-width line with no key column.

Keeping it current

name: refresh-card
on:
  schedule: [{ cron: "0 5 * * *" }]
  workflow_dispatch:

permissions:
  contents: write

jobs:
  refresh:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install pillow
      - run: python -m termfetch --config config.json --out card.svg
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add card.svg
          git diff --cached --quiet || { git commit -m "Refresh profile card"; git push; }

GITHUB_TOKEN is optional but raises the API rate limit from 60 requests/hour per IP to 5,000, which matters if you use languageMode: "bytes".

How it works

  1. Sample. The image is cropped, resized to the character grid, and each cell becomes a character picked from a luminance ramp plus the cell's average colour.
  2. Quantise. Colours are snapped to a coarser grid so runs of neighbouring cells share a value and collapse into one SVG span. This is most of the difference between a 60 KB file and a 300 KB one.
  3. Position every glyph explicitly. SVGs embedded in a README render as images, so webfonts never load and the viewer's monospace fallback is whatever their OS provides. Relying on the font's own advance width would let columns drift apart on some machines, so each span carries an absolute x.

Limitations

  • Character cells are assumed to be 0.6× as wide as they are tall. That holds for the fonts in the stack; a wildly different fallback would shear the art slightly.
  • Colour is per character cell, so effective resolution is imageCols × about 0.6 × imageCols. Portraits work; fine detail and text in the source image do not.
  • Stats come from the public REST API, so private repositories and private contributions are not counted.

Development

python -m pip install -e '.[dev]'
pytest -q

Contributions are welcome. See CONTRIBUTING.md for the development and pull request checklist, and CHANGELOG.md for release notes.

Licence

MIT — see LICENSE. The example photograph is not covered by it; replace it with your own.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

termfetch-0.1.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

termfetch-0.1.0-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file termfetch-0.1.0.tar.gz.

File metadata

  • Download URL: termfetch-0.1.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for termfetch-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e78155a85b5886a9cda108576538c0dc1dc7854b01950580b50921dbaf2596b9
MD5 2d49cf31eb8dec07f0296056756f2a79
BLAKE2b-256 c94197e1033b41de1731786259649b64095246cb9ca4ee25d9ed5bc41d5ed393

See more details on using hashes here.

Provenance

The following attestation bundles were made for termfetch-0.1.0.tar.gz:

Publisher: release.yml on saivarun1410/termfetch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file termfetch-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: termfetch-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for termfetch-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a8a8831429a077ec36973a7e2663a936204418c98330a73727b8737d1e8d2bec
MD5 ef3c7e481c2effefe8e984eba1594f9c
BLAKE2b-256 37bf3d2408033601715932d06257acf879554f64dcce8e9f4eb2a9adcef8f007

See more details on using hashes here.

Provenance

The following attestation bundles were made for termfetch-0.1.0-py3-none-any.whl:

Publisher: release.yml on saivarun1410/termfetch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page