bewitch
A Python port of Charm's Lip Gloss and charmtone, made because we liked them so much — with the drawn parts of Bubbles and the look of Crush on top.
Not affiliated with or endorsed by Charm. The module names are theirs on
purpose: bewitch.lipgloss is our port of Lip Gloss, bewitch.charmtone of
charmtone, bewitch.bubbles of the drawing half of Bubbles. Naming them so is
the credit — Charm's work is MIT-licensed, we copied it into another language,
and we would rather say so than file the names off. See
THIRD_PARTY.md.
from bewitch import bubbles, charmtone
from bewitch.lipgloss import Style, rounded_border
card = (
Style()
.foreground(charmtone.Butter)
.background(charmtone.Charple)
.padding(0, 1)
.border(rounded_border())
.border_foreground(charmtone.Dolly)
.width(30)
)
print(card.render("Chillhop Essentials · Winter 2025"))
print(bubbles.header("ytq", ["lofi hip hop", "20"], width=40))
What it is, and what it is not
bewitch draws. It does not read keys, run an event loop or hold state —
the app does that, on whatever TUI library it uses (blessed, for ytq and dlq),
and bewitch gives it Crush's look. A render is a Block of styled spans rather than a string full
of escape sequences, so the same render can go to:
| Adapter | For |
|---|---|
bewitch.ansi |
print(), and anything that takes ANSI strings (blessed, Rich's Text.from_ansi) |
bewitch.screen.Frame |
full-screen apps, on blessed or anything that can write to the terminal: a whole screen per frame, only the changed lines written, nothing at all when nothing changed |
| (planned) Rich | Textual and Rich |
Modules
| Module | Ports | What it does |
|---|---|---|
bewitch.lipgloss |
Lip Gloss v2.0.6 | Style (colours, attributes, padding, margin, borders, width, height, alignment), the border sets, join_horizontal / join_vertical / place, Canvas, width / truncate, blend_1d |
bewitch.charmtone |
charmtone v0.1.0 | Charm's 70 colours, Charm's names (charmtone.Charple), Charm's groups |
bewitch.bubbles |
Bubbles v2.2.1 (drawing only) | spinners, help line, progress bar, paginator, scrollbar, list rows; from huh, the checkbox; and, from Crush, the header band, section rules, dialog titles, labels and status rows |
bewitch.colorprofile |
colorprofile v0.4.3 | truecolor / 256 / 16 / none, from the environment; colours fitted to each |
bewitch.theme |
— | colours by role (primary, fg_muted, separator…), Crush's mapping by default |
bewitch.glyphs |
— | the glyphs Crush draws with, each tested on a phone, each with an ASCII stand-in |
The house style
docs/house-style.md is the standard every screen built on bewitch follows: the anatomy of a screen, colour by role, selection, marks, dialogs, glyphs, how things give way at 32 columns, and how to move a curses screen over.
The gallery
python -m bewitch # at this terminal's width
python -m bewitch --width 40 # at 40 columns
python -m bewitch --color 256 # as a 256-colour terminal would show it
Draws every piece in the terminal you run it in. Every line ends at the same column, so a glyph your font draws two columns wide shows up as a ragged right edge on its line. Run it on the device a screen is for — that is the test no suite can run.
Colour
Detection follows Charm's colorprofile, plus three rules:
- Termux is truecolor, although it reports
TERM=xterm-256color. BEWITCH_COLOR(truecolor,256,16,ascii,none) overrides everything, andNO_COLORis honoured.- Without colour, a background becomes reverse video, so a selection bar never disappears with the colour.
Using it
bewitch is standard library only. Install it from PyPI:
pip install bewitch
or use it from a checkout — put the checkout on sys.path, the way nibbles
finds it — or install that with pip install -e ..
Releasing
A push to main that changes version in pyproject.toml to one PyPI does
not have yet is a release: .github/workflows/release.yml runs the suite,
builds, publishes through PyPI's trusted publishing (no token is stored
anywhere) and tags v<version>. Bump __version__ in bewitch/__init__.py
with it; a test holds the two together.
Developing
make dev # uv sync: pytest and hypothesis into .venv
make test # the suite (also .githooks/checks.sh, which the pre-push hook runs)
make lint # ruff
make golden # re-record the reference renders after a deliberate visual change
Enable the hook once per clone: git config core.hooksPath .githooks.
Licence
MIT. Lip Gloss, charmtone, Bubbles and colorprofile are MIT-licensed by Charmbracelet, Inc.; their notice is in THIRD_PARTY.md.
Metadata
Release files for bewitch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bewitch-0.1.0.tar.gz | 57.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bewitch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 107.1 kB
Release files / bewitch-0.1.0.tar.gz
| Download URL | bewitch-0.1.0.tar.gz |
|---|---|
| Size | 57.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
664ff4d0d576238b2f5113636aa8016e5c1ee7679c108b4434eb60361c55e6fd
|
|
BLAKE2b-256 checksum How to use checksums |
62ee9d1bbbc8dab4c9c4bb94db602164a78d5dc08644a8879076f71cbfdb5aa0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / bewitch-0.1.0-py3-none-any.whl
| Download URL | bewitch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 49.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b46569fa0b8c5b0a1bc45f28cfb0a1eda635475e72e3487b6d561b221beb1217
|
|
BLAKE2b-256 checksum How to use checksums |
1b867afede8d047219903fdde2951ad1060fd2e54d3caa735570abfb2ed81e71
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log