Design and render terminal/CMD visual effects and ASCII art from a one-line request via the pluggable `fx` engine (30 effects, hot-swappable and themeable, plus scripted shows). Effects include donut, matrix rain, plasma, fire, a spinning 3D ball, a solar-system orrery, Game of Life, wireframe cube, 3D text banners, rainbow/lolcat gradient text, starfield, tunnel, fireworks, image-to-ASCII, and more. Use when the request is for a terminal animation, ANSI/CLI art, or a new console effect. Pure...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add dwgx/SmartCLI --skill cmd-art --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cmd Art?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/dwgx-cmd-art)More formats (shields.io, HTML) on the badges page.
---
name: cmd-art
description: >-
Design and render terminal/CMD visual effects and ASCII art from a one-line
request via the pluggable `fx` engine (30 effects, hot-swappable and
themeable, plus scripted shows). Effects include donut, matrix rain, plasma, fire, a
spinning 3D ball, a solar-system orrery, Game of Life, wireframe cube, 3D text
banners, rainbow/lolcat gradient text, starfield, tunnel, fireworks,
image-to-ASCII, and more. Use when the request is for a terminal animation, ANSI/CLI art, or
a new console effect. Pure Python stdlib; truecolor.
version: 0.2.3
---
# cmd-art
Render and design terminal visual effects with `fx`, a living-template engine: an `Effect` ABC + a `@register` decorator + pkgutil auto-discovery, so effects, themes, and multi-effect shows all compose. Pure Python stdlib (optional `pyfiglet` for fonts, `PIL` for real images). Truecolor + ANSI, tuned for Windows Terminal but works on any VT-capable terminal.
## When to use
- User asks for a terminal/CMD animation or ANSI art: "spinning ball", "donut", "matrix rain", "plasma", "fire", "game of life", "3D title", "rainbow banner", "starfield", "tunnel", "fireworks".
- User wants a static ASCII title/banner with a color gradient, or an image turned into ASCII.
- User wants to invent a new console effect (drop one file in `fx/effects/`).
## The 30 effects (registry names)
Run `python -m fx list` for the live catalog. Names + aliases as registered:
| name | kind | what | aliases |
|------|------|------|---------|
| `sphere` | anim | rotating Lambert-shaded 3D ball ("spinning ball") | |
| `donut` | anim | THE spinning ASCII torus (Sloane donut.c) | |
| `solarsystem` | anim | orrery: planets on elliptical orbits round a pulsing sun | `orrery`, `planets` |
| `cube` | anim | rotating wireframe cube | |
| `tunnel` | anim | demoscene tunnel fly-through | |
| `starfield` | anim | perspective starfield warp | |
| `plasma` | anim | full-screen sine interference field | `wave` |
| `julia` | anim | animated Julia-set fractal (morphing c) | `juliaset` |
| `mandelbrot` | anim | infinite zoom into the Mandelbrot set | `mandel` |
| `perlin` | anim | flowing Perlin gradient-noise field (clouds) | `noise`, `clouds` |
| `flames` | anim | noise-convection fire, black-body color (vs cellular `fire`) | `firefield`, `bonfire` |
| `water` | anim | water surface: sum-of-sines swell + caustic net | `ocean`, `waves` |
| `nebula` | anim | domain-warped gas nebula: filaments, multi-color, stars | `galaxy`, `cosmos` |
| `spectrum_bars` | anim | audio-style spectrum meter: log bins, gravity smoothing, eighth-blocks | `spectrum`, `bars` |
| `text_flyin` | anim | text intro: characters fly in and land | `flyin` |
| `text_converge` | anim | text intro: characters converge from a scattered cloud | `converge` |
| `text_decrypt` | anim | text intro: cells churn glyphs, then lock to the word | `decrypt_text` |
| `fire` | anim | demoscene fire (heat buffer + cooling) | |
| `rain` | anim | matrix digital rain | |
| `life` | anim | Conway's Game of Life, toroidal | `game_of_life` |
| `boids` | anim | Reynolds flocking | |
| `cbonsai` | anim | procedural ASCII bonsai grown by a stochastic branching turtle | |
| `fireworks` | anim | rising shells → gravity sparks | |
| `sparkle` | anim | dim text under bright glints | |
| `text3d` | anim* | big figlet/block banner, gradient; shimmer animates | `banner` |
| `gradient_text` | anim | lolcat rainbow / theme-gradient text | `lolcat` |
| `banner_scroll` | anim | scrolling figlet marquee | `marquee` |
| `typewriter` | anim | char-by-char reveal with a cursor | |
| `decrypt` | anim | scramble-to-plaintext reveal | |
| `image2ascii` | static | image → half-block truecolor / ASCII ramp | `img` |
`*text3d` is static unless `--set shimmer=true`. `image2ascii` is a `StaticEffect` (one frame to the normal screen). Everything else animates in the alt-screen.
## How to run
From `skills/cmd-art`, run the package:
```
python -m fx <command> [args]
```
Or by path from any cwd (a PEP-366 prelude bootstraps the package):
```
python skills/cmd-art/fx/cli.py <command> [args]
```
Legacy compat shim (preserves the old surface, translates to `fx play`):
```
python skills/cmd-art/scripts/ascii_fx.py [--fps N] [--seconds N] [--once] [--width W] [--height H] <sphere|plasma|wave|rain|text3d> [args]
```
Prefer the `fx` CLI for new work; use the shim only for old commands/imports.
## Commands
- `list [--tag T] [--json]` — every registered effect (kind, tags, description, aliases). `--tag` filters (e.g. `3d`, `text`, `particles`, `classic`, `field`, `matrix`). `--json` emits full param schemas.
- `show <name>` — one effect's details: animated?, tags, aliases, preferred theme, and every `--set` param (kind, default, choices, range, help), plus a ready-to-run example. `show` also plays scripted shows via `--seq`/`--script` (see Compose a show).
- `play <name> [flags]` — play one effect. **Bounded by default** (10s on a TTY when no `--seconds`/`--frames`). Flags:
- `--theme NAME` — palette (see Themes). Defaults to the effect's `preferred_theme`.
- `--seconds N` — stop after N seconds.
- `--frames N` — stop after N frames.
- `--once` — render ONE frame to the normal screen and exit (best for `text3d`, `image2ascii`, previews).
- `--forever` — explicitly unbounded (until Ctrl-C).
- `--fps N` — frame rate (default: the effect's own `default_fps`).
- `--width W` / `--height H` — `0` = terminal size (height leaves 1 row headroom).
- `--set K=V` — override an effect param (repeatable). Unknown keys fail loudly.
- `gallery [--seconds-per N] [--tag T] [--theme NAME]` — tour every animated effect back-to-back in one alt-screen (default 3s each).
- `random [play-flags]` — play a random animated effect with a random theme (defaults to 8s). Ignores `--once` (always plays the animation).
Non-TTY safety: `play`/shows degrade to a single plain frame when stdout is not a terminal, and the play loop always restores the terminal (cursor, wrap, alt-screen) via try/finally. Prefer `--seconds`/`--once` in automated contexts so the process always terminates.
## Themes
`--theme` names (from `fx/theme.py`, live via `theme_names()`):
`mono`, `fire`, `ocean`, `synthwave`, `viridis`, `pastel`, `matrix-green`, `rainbow`.
Default theme is `synthwave`. Effects never bake in hex; they sample the active `Theme` (`color_at(t)` gradient, `cycle(phase)` HSV/lolcat sweep, `.primary`/`.accent`/`.base`), so swapping the theme restyles every effect for free. Palette caveat: `flames`, `water`, and `nebula` default to a built-in physical palette (`blackbody` / `ocean` / `cosmic`) and only follow `--theme` with `--set palette=theme`; `plasma`, `julia`, `mandelbrot`, and `perlin` default to `palette=theme`. An unknown name passed to the `--theme` flag (`play`, `show`, `gallery`, `random`) fails loud; only an unknown theme name inside a `--seq`/`--script` segment slot silently falls back to the default.
## Example commands
- Spinning ball, 5s, ocean: `python -m fx play sphere --seconds 5 --theme ocean`
- THE donut, faster spin: `python -m fx play donut --set speed=2 --seconds 8`
- Matrix rain: `python -m fx play rain --seconds 8`
- Fire, shorter/calmer flames: `python -m fx play fire --set cool=2`
- Game of Life, dense start: `python -m fx play life --set fill=0.35 --seconds 10`
- 3D title, one static frame: `python -m fx play text3d --set text=KIRO --once`
- Shimmering animated title: `python -m fx play text3d --set text=KIRO --set shimmer=true --seconds 6`
- Rainbow lolcat text: `python -m fx play gradient_text --set text="hello" --theme rainbow`
- Image → ASCII (half-block): `python -m fx play image2ascii --set path=pic.png --once`
- Tour everything: `python -m fx gallery --seconds-per 3`
- Surprise me: `python -m fx random`
## Compose a show
Play a timed sequence of segments in ONE alt-screen (no flicker between segments) via `show`:
- Compact `--seq` grammar `effect[:theme[:seconds]],...`, with `left|right` split-screen in the effect slot:
```
python -m fx show --seq "donut:fire:4,plasma::3,rain:matrix-green:5"
python -m fx show --seq "donut|fire:synthwave:6" # donut left, fire right
```
- JSON `--script show.json` (list of segment objects):
```json
[
{"effect": "donut", "theme": "fire", "seconds": 4, "params": {"speed": 2}},
{"split": ["donut", "fire"], "themes": ["ocean", "fire"], "seconds": 6}
]
```
`--seconds-per` sets the default per-segment duration (default 4s). A segment that omits a theme uses its effect's `preferred_theme` (default `synthwave` otherwise); the `show`/`gallery` `--theme` flag is validated but does not currently affect segment colors. Each segment gets a local clock (t restarts at 0). `Split` is a combinator built from two real effects, not a registered effect.
## Knowledge base — look before you build
**Not sure how to render something? Start at `knowledge/effects/choosing-an-effect.md`**
— it maps "I want to show X" to a direction, the exact-formula note, and the
shipped effect you can reuse or take apart (reuse as-is, tune params, or compose
your own from the primitives it cites). From there, or directly:
Before inventing or tuning an effect, consult the SmartCLI knowledge graph at
`knowledge/INDEX.md` (repo root, two levels above this skill's folder). Its
`effects/` domain carries exact-formula
notes for most of what ships here — [[donut-torus]], [[plasma]], [[fire-lode]] /
[[fire-doom-psx]], [[matrix-rain]], [[game-of-life]], [[boids]], [[starfield]],
[[tunnel]], [[rotating-cube]], [[ascii-luminance-ramp]] — plus [[color-interpolation]]
and [[hsv-cycling-lolcat]] for theme/lolcat color. The `works/` wing ([[donut-c]],
[[lolcat]], [[cmatrix]], [[terminaltexteffects]], ...) is the design brain for a NEW
effect: pick your lane — **replica** a real effect by measuring its source math, or
**compose** a new one from documented primitives. Read the relevant note before writing
render code from scratch; `references/effects.md` is the local techniques sampler, the
graph is the sourced depth.
## Add a NEW effect
Drop one module in `fx/effects/` — pkgutil auto-imports it, `@register` wires it into `list`/`show`/`play`/`gallery`. A module that fails to import is reported (stderr + `registry.load_errors()`) but never takes the catalog down.
Minimal contract (`fx/base.py`):
- Subclass `Effect` (or `StaticEffect` for one-shot art). Set class metadata: `name` (lowercase, unique registry key), `description`, `tags`, optional `aliases`, `preferred_theme`, `default_fps`, and a `params` tuple of `Param(...)`.
- `Param(name, kind, default, help, choices=None, min=None, max=None)` — `kind` ∈ `int float str bool color`. The CLI lists/parses/validates `--set` against these; unknown keys and out-of-range values fail loud. `color` accepts `#RRGGBB`/`RRGGBB` (empty = None).
- Implement `render(self, ctx: FrameCtx) -> str`: return ONE full frame — `ctx.height` rows joined by `\n`, no trailing newline, EVERY cell written. Effects are pure frame producers: never print, sleep, or touch ANSI modes — the play loop owns the terminal.
- `ctx: FrameCtx` fields: `t` (seconds since start), `frame_index`, `width`, `height`, `theme` (active `Theme`, never None), `params` (coerced dict).
- Optional lifecycle: `setup()` (allocate buffers/particles before the first frame) and `teardown()` (release state; also runs on error/Ctrl-C). Override `is_animated(cls, params)` when animation depends on a param — in either direction: `text3d` is static by default and only animates with `--set shimmer=true`; `gradient_text` animates by default and goes static with `--set drift=0`.
Skeleton:
```python
from fx.base import Effect, FrameCtx, Param
from fx.registry import register
@register
class Swirl(Effect):
name = "swirl"
description = "example: a themed diagonal sweep."
tags = ("field", "demo")
default_fps = 24.0
params = (Param("speed", "float", 1.0, "sweep speed", min=0.0, max=10.0),)
def render(self, ctx: FrameCtx) -> str:
rows = []
for y in range(ctx.height):
cells = []
for x in range(ctx.width):
t = ((x + y) / max(1, ctx.width + ctx.height)
+ ctx.t * ctx.params["speed"] * 0.1) % 1.0
r, g, b = ctx.theme.color_at(t)
cells.append(f"\x1b[38;2;{r};{g};{b}m#")
rows.append("".join(cells) + "\x1b[0m")
return "\n".join(rows)
```
Then `python -m fx play swirl --theme rainbow --seconds 5`. Duplicate names raise `RegistryError` (pass `@register(replace=True)` to intentionally override a built-in).
## Constraints / gotchas
- Keep frames exactly `ctx.height` rows and `ctx.width` cells; auto-size leaves 1 row headroom so the top doesn't scroll off.
- `plasma` and full-field effects are the heaviest (O(width×height) escapes/frame); shrink dimensions or lower `--fps` if they stutter.
- `pyfiglet` powers `text3d`/`banner_scroll`/`gradient_text --set big=true` if importable; otherwise a built-in block font covers A-Z, 0-9, space. `image2ascii` uses `PIL` if present, else a PNM/built-in demo fallback.
- ANSI on Windows is enabled automatically before every `play`/`render_once` (`enable_vt()`), no action needed.
- `references/effects.md` is a **techniques sampler** — it works a few effects in depth (sphere projection, block-text gradient, plasma field, matrix rain), not one section per shipped effect. Read it, plus the per-effect formula notes in `knowledge/effects/` (see Knowledge base above), when adding or tuning an effect.
- Path bases: `fx/`, `references/`, `tmux/`, and `scripts/` are relative to this skill's folder; `knowledge/` is relative to the repo root (two levels above it).
## Optional tmux launchers
The play loop already owns a single alt-screen session, so tmux is never required. For convenience, `tmux/` ships two POSIX-sh launchers that drop an effect into a tmux popup or split pane:
- `tmux/fx-popup.sh <effect|gallery|random> [fx args]` — `display-popup -E` (tmux >= 3.2).
- `tmux/fx-split.sh [-v] EFFECT [EFFECT2] [fx args]` — split the current window (one effect in a new pane, or a 2-up of two).
Both guard on `command -v tmux`: with no tmux they print the direct/compositor alternative plus install hints and exit `0`. `play`/`random` runs get a default `--seconds 10` bound. Truecolor through tmux needs `set -g default-terminal "tmux-256color"` + `set -ga terminal-overrides ",*:Tc"`. See `tmux/README.md`. For a seamless side-by-side without tmux, use the compositor: `python -m fx show --seq "donut|fire:synthwave:6"`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!