Stdlib-only CLI primitives — colors, progress, prompts, spinners, emoji helpers — with consistent UX across Codechu tools.
Project description
codechu-cli
___ _ _
/ __\___ __| | ___ ___| |__ _ _
/ / / _ \ / _\` |/ _ \/ __| '_ \| | | |
/ /__| (_) | (_| | __/ (__| | | | |_| |
\____/\___/ \__,_|\___|\___|_| |_|\__,_|
Stdlib-only CLI primitives for Python — colors, progress bars, spinners, prompts, emoji helpers — with consistent UX across Codechu tools.
pip install codechu-cli
What it gives you
Color— ANSI palette withNO_COLOR+ TTY detectionProgressLine— single-line overwriting stderr progressProgressBar— bracketed bar with percent + countsSpinner— threaded spinner (braille frames + ASCII fallback)confirm/prompt— yes-no + single-line input (with validator + password mode)select/multiselect— arrow-key pickers (numbered fallback off-TTY)banner— single-line header (TTY-only)resolve_format/format_examples— argparse output-format helpersemoji.capabilities/emoji.e— locale + terminal-aware glyph lookup
Pure stdlib. POSIX-first. Linux/macOS for raw-mode pickers; numbered prompt fallback everywhere else (Windows, CI, pipes).
Quick examples
Color + banner
import sys
from codechu_cli import Color, banner
banner("disk-cleaner", "1.2.0", mode="dry-run")
c = Color(sys.stderr)
print(c("low", "ok") + " scan complete")
print(c("high", "ERR") + " refusing destructive op")
Progress bar
from codechu_cli import ProgressBar
bar = ProgressBar(total=100, width=30)
for chunk in stream_chunks():
process(chunk)
bar.advance(1, label=chunk.name)
bar.finish()
Spinner
from codechu_cli import Spinner
with Spinner("Scanning…"):
walk_filesystem()
Styles (ProgressBar + Spinner)
Both ProgressBar and Spinner accept a named style= preset so you
don't have to remember glyphs. Explicit fill/empty/frames still
win — style just supplies defaults.
from codechu_cli import ProgressBar, Spinner
# Industry-standard
ProgressBar(100, style="block") # █░ — npm/cargo
ProgressBar(100, style="ascii") # #- — GitHub Actions
ProgressBar(100, style="equals") # =- — Docker
# Codechu signature
ProgressBar(100, style="codechu") # ▰▱ — matches disk-cleaner UI
# Spinner styles — industry classics
Spinner("…", style="dots") # ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ braille (default)
Spinner("…", style="dots2") # ⣾⣽⣻⢿⡿⣟⣯⣷ bolder braille
Spinner("…", style="line") # |/-\ ASCII safe
Spinner("…", style="arc") # ◜◠◝◞◡◟
Spinner("…", style="pulse") # • ◦ ◦
# Spinner styles — N-cell block patterns (Claude Code-style)
Spinner("…", style="blocks-bounce") # ▰▱▱▱▱ → ▱▰▱▱▱ → … 5-cell bouncing
Spinner("…", style="blocks-fill") # ▱▱▱▱▱ → ▰▰▰▰▰ fill cycle
Spinner("…", style="blocks-snake") # fill + drain (10 frames)
Spinner("…", style="blocks-pulse") # ▱▱▱▱▱ ↔ ▰▰▰▰▰
Spinner("…", style="arrow3") # ▸▹▹▹▹ → ▹▸▹▹▹ → 5-cell arrow scan
# Spinner styles — 3-cell variants (fit inline next to a label)
Spinner("…", style="dots3") # . → .. → ...
Spinner("…", style="wave3") # ▁▂▃ vertical wave
Spinner("…", style="tri3") # ◐◯◯ → ◯◐◯ → ◯◯◐
# Spinner styles — single-cell pulses
Spinner("…", style="grow-h") # ▏▎▍▌▋▊▉█ horizontal grow (subpixel)
Spinner("…", style="grow-v") # ▁▂▃▄▅▆▇█ vertical grow
Spinner("…", style="toggle") # ■ ↔ □
Spinner("…", style="codechu") # ◐◓◑◒ disk-quarters, Codechu signature
Spinner("…", style="codechu-fade") # ▒▓█▓ solid pulse
# Pictographic (terminal must support emoji)
Spinner("…", style="earth") # 🌍🌎🌏
Spinner("…", style="moon") # 🌑🌒🌓🌔🌕🌖🌗🌘
Spinner("…", style="clock") # 🕐🕑🕒…
# Fixed small-block (Claude Code-style polish — width baked in)
ProgressBar(100, style="blocks") # ▰▰▰▱▱ width=5
ProgressBar(100, style="blocks-wide") # 8 cells
ProgressBar(100, style="claude") # █░ at width=10
# Subpixel-smooth — narrow bar that still shows fractional progress
ProgressBar(100, style="smooth") # 10-cell, 80 distinct stops
ProgressBar(100, style="smooth-wide") # 20-cell, 160 distinct stops
# Full override still works (style provides defaults; fill/empty/frames override)
ProgressBar(100, style="block", fill="*")
Spinner("…", style="dots", frames=["A", "B", "C"])
Fixed-block styles bake their width into the preset (Claude Code-style polish: 5–10 cells); smooth styles use Unicode eighth-block partial fills so a 10-cell bar still has 80 distinct steps.
When you don't know the total upfront, prefer Spinner over
ProgressBar — it signals "work in progress" without misleading
percentages. Reach for ProgressBar only when you can count items
honestly.
The full registries are exported as SPINNER_STYLES and BAR_STYLES
for introspection (e.g. for --style help text in a CLI).
Timing helpers (sibling libraries)
The timing primitives that used to live in codechu_cli.timing now
live in three single-responsibility sibling libraries — pulled in
automatically as dependencies:
codechu-fmt— human-readable formatters (format_duration,format_rate,format_size)codechu-meter— measurement primitives (Stopwatch,RateEstimator,ETAEstimator)codechu-spark— text visualizations (sparkline,bar_chart,heatmap)
For convenience, the most common names are re-exported from
codechu_cli so existing code keeps working:
from codechu_cli import format_duration # → codechu-fmt
format_duration(90) # → '1m 30s'
from codechu_cli import Stopwatch # → codechu-meter
with Stopwatch() as sw:
do_work()
print(sw) # → '1m 30s'
from codechu_cli import sparkline # → codechu-spark
sparkline([1, 3, 7, 2, 5]) # → '▁▃█▂▅'
See each sibling library's README for the full API. ProgressBar's
{elapsed}, {eta}, {rate}, {remaining} template fields
delegate to codechu-fmt and codechu-meter internally.
Indeterminate mode — when you don't know the total upfront:
bar = ProgressBar(total=None) # animated sliding bar
for chunk in stream:
bar.advance()
bar.set_total(known_total) # switches to normal rendering
Complete catalog
Spinner families currently shipped:
classic(7),codechu(2),blocks(5),compact(7),grow(2),pictographic(2),modern(9),chaos(7),random-access(6),matrix(4),quadrant(5),conveyor(4),iconic(5)loading(3) —bar,buffering,signalsemantic(4) —heartbeat,searching,atom,spiraloutro(3) —success-flash,error-pulse,retry-slow
Timing helpers live in sibling libs — see the "Timing helpers (sibling libraries)" section above.
Confirm + prompt
from codechu_cli import confirm, prompt
if not confirm("Delete 12 GB of caches?", default=False):
raise SystemExit(0)
name = prompt("Backup name", default="snapshot-1")
def positive(x: str) -> None:
if not x.isdigit() or int(x) <= 0:
raise ValueError("must be a positive integer")
retries = prompt("Retries", default="3", validate=positive)
secret = prompt("Token", password=True)
Single + multi select
from codechu_cli import select, multiselect
mode = select("Cleanup mode", [
("Conservative — caches only", "safe"),
("Aggressive — also logs", "aggressive"),
("Custom", "custom"),
])
targets = multiselect("Pick targets", [
"~/.cache/pip", "~/.cache/yarn", "~/.cargo/registry",
], defaults=(0, 1))
On non-TTY (CI, pipes) both fall back to a numbered-prompt UI — no extra branching in caller code.
Output format detection (argparse)
import argparse, sys
from codechu_cli import format_examples, resolve_format
p = argparse.ArgumentParser(
epilog=format_examples([
("disk-cleaner scan", "scan default mounts"),
("disk-cleaner scan --json", "machine-readable output"),
]),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--format", default=resolve_format(sys.stdout))
Emoji helpers
from codechu_cli import capabilities, e
caps = capabilities() # {"unicode", "color", "emoji"} or subset
print(f"{e('ok')} done") # "✓ done" or "+ done"
print(f"{e('arrow')} next step") # "→ next step" or "-> next step"
CODECHU_CLI_EMOJI=never forces ASCII, always forces unicode.
API reference
| Symbol | Purpose |
|---|---|
Color(stream) |
ANSI palette; respects NO_COLOR + isatty() |
banner(title, version, *, mode=None, stream=...) |
TTY-only header |
ProgressLine(stream=None, enabled=None) |
.update() / .clear() |
ProgressBar(total, *, width=40, ...) |
.advance(n, label) / .set_total(n) / .finish() |
Spinner(message, *, frames=None, interval=0.08) |
context manager + .start()/.stop() |
confirm(prompt, *, default, assume_yes, ...) |
yes/no |
prompt(message, *, default, validate, password, ...) |
single-line input |
select(message, choices, *, default=0, ...) |
single-choice picker |
multiselect(message, choices, *, defaults=(), ...) |
multi-choice picker |
resolve_format(stream, *, tty_default, pipe_default) |
format chooser |
format_examples([(cmd, desc), ...]) |
argparse epilog |
capabilities(stream=None) |
set of {"unicode", "color", "emoji"} |
e(name, *, fallback=None) |
glyph lookup with ASCII fallback |
Extension points
The library is opinionated about defaults but accepts customization at every visible surface, so apps don't need to subclass to fit their brand or locale.
# Custom palette (merged onto defaults)
c = Color(sys.stdout, palette={
"snap-edge": "\033[35m",
"snap-stable": "\033[36m",
})
print(c("snap-edge", "edge channel"))
# Force color on/off (override NO_COLOR + TTY detection)
c = Color(sys.stdout, force=True)
# Custom progress bar look
bar = ProgressBar(100, fill="█", empty="░",
template="{bar} {pct}% · {elapsed} · ETA {eta}")
for _ in range(100):
bar.advance(label="working…")
bar.finish()
# Turkish confirm
from gettext import gettext as _
confirm("Devam edilsin mi?",
yes_chars=("e", "evet"),
no_chars=("h", "hayır"),
translate=_)
# Register custom emoji
from codechu_cli import emoji
emoji.register("snap", "📦", "snap")
print(emoji.e("snap")) # 📦 or "snap" depending on capabilities
# vi-only keymap for select
select("Pick branch", branches,
keymap={"up": ("k",), "down": ("j",)})
Raw ASCII-art banners
ascii_banner(art, ...) prints a multi-line ASCII-art string with
optional color. Text-to-art generation (rendering a plain string like
"DISK" as multi-line glyph blocks via a font) is out of scope for
this library — that's a typography problem with its own quality
trade-offs. Future plugin libraries under the codechu-glyph-*
namespace will own that, depending on codechu-cli for the rendering
plumbing.
For now: bring your own art, or pick from the LOGOS registry:
from codechu_cli import LOGOS, ascii_banner
ascii_banner(LOGOS["codechu"], color="info")
ascii_banner(my_own_art_string, color="dim")
Internationalization
The library ships English defaults and does not bundle gettext / .po files — that's an application concern. For the handful of strings it emits autonomously (the select / multiselect hint line, the confirm suffix, the prompt validator error, the numbered-fallback "Enter your choice"), inject a translator callable:
from gettext import gettext as _
confirm("Devam edilsin mi?", translate=_)
select("Bir seçin", choices, translate=_)
multiselect("Hedefler", targets, translate=_)
prompt("Yedek adı", validate=v, translate=_)
This follows the STANDARDS.md §11 library carve-out: libraries accept a translator hook rather than shipping their own catalog.
Discover
With 65 spinner styles, finding the right one is the hard part.
# Preview every style live
python -m codechu_cli demo
# Filter by family (13 families: classic, chaos, matrix, conveyor, …)
python -m codechu_cli demo --family chaos
# Filter by mood tag (calm, busy, chaotic, playful, minimal, narrow, wide)
python -m codechu_cli demo --tag calm
# Just list everything
python -m codechu_cli list
Or programmatically:
from codechu_cli import SPINNER_FAMILIES, STYLE_TAGS, STYLE_COMPATIBILITY
print(SPINNER_FAMILIES["chaos"])
# ['static', 'storm', 'sparks', 'fireworks', 'electricity', 'maelstrom', 'build-up']
# Pick one safe for any terminal
import random
safe = STYLE_COMPATIBILITY["ascii-safe"]
spinner_style = random.choice(list(safe))
You can also register your own at runtime:
from codechu_cli import register_spinner_style, register_bar_style
register_spinner_style(
"my-spin", ["◐", "◓", "◑", "◒"],
family="codechu", tags={"calm"},
)
register_bar_style("my-bar", fill="▰", empty="▱", width=10)
Stability
Style names (the keys in SPINNER_STYLES, BAR_STYLES) are part of the
public API. Adding new names is non-breaking; removing or renaming
requires a deprecation cycle (warn for one minor version, then remove
on the next). Family names and tag names follow the same rule.
License
MIT — see LICENSE.
Part of Codechu.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file codechu_cli-0.1.0.tar.gz.
File metadata
- Download URL: codechu_cli-0.1.0.tar.gz
- Upload date:
- Size: 44.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d747e20d92b51497cd8785ed39bcd2b93726ea3729a1ad7a5d29e5c2f53e2f56
|
|
| MD5 |
663274b65bfd82c08701170df08c973f
|
|
| BLAKE2b-256 |
b480c7935c427e3ac4b06789c21499cf0c388d138ef43f25aa0052312d7d3b86
|
Provenance
The following attestation bundles were made for codechu_cli-0.1.0.tar.gz:
Publisher:
release.yml on codechu/cli-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codechu_cli-0.1.0.tar.gz -
Subject digest:
d747e20d92b51497cd8785ed39bcd2b93726ea3729a1ad7a5d29e5c2f53e2f56 - Sigstore transparency entry: 1580210608
- Sigstore integration time:
-
Permalink:
codechu/cli-py@0c8eabd558e51fa8496251827f350c9fb78e756b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/codechu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0c8eabd558e51fa8496251827f350c9fb78e756b -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file codechu_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: codechu_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b109a4fc32f56fec36b1ffe27cc248490dcc01134ab29b3523a9d56f68f0780
|
|
| MD5 |
64e74c5ffb9d0f4d3d278771a8ef6016
|
|
| BLAKE2b-256 |
6e6ffb4a4b61e2ff090d193dc804c11b50d65d07ec3ec860db4e76bb95c6477a
|
Provenance
The following attestation bundles were made for codechu_cli-0.1.0-py3-none-any.whl:
Publisher:
release.yml on codechu/cli-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codechu_cli-0.1.0-py3-none-any.whl -
Subject digest:
4b109a4fc32f56fec36b1ffe27cc248490dcc01134ab29b3523a9d56f68f0780 - Sigstore transparency entry: 1580211005
- Sigstore integration time:
-
Permalink:
codechu/cli-py@0c8eabd558e51fa8496251827f350c9fb78e756b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/codechu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0c8eabd558e51fa8496251827f350c9fb78e756b -
Trigger Event:
workflow_dispatch
-
Statement type: