Skip to main content

tui-ui

Design web-like user interfaces in the terminal and inside tmux with a cell-grid Canvas, CSS-like box model, flexbox/grid layout, and 15 reusable widgets such as Panel, Table, Card, ProgressBar, Meter, Tabs, Tree, Badge, Banner, and a braille line chart. Use when an agent needs a dashboard, panel, table, status page, TUI layout, tmux dashboard, screenshot-driven CLI/TUI replica, ANSI frame, truecolor render, pyte PNG screenshot smoke test, wide-character alignment, or a new terminal widget.

跳到安装

来源信息

仓库
mediar-ai/skillhubz
最近来源活动
2026年7月11日 07:47
检测到的 SKILL.md 语言
英语
星标
7
分支
4

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
tui-ui
description
Design web-like user interfaces in the terminal and inside tmux with a cell-grid Canvas, CSS-like box model, flexbox/grid layout, and 15 reusable widgets such as Panel, Table, Card, ProgressBar, Meter, Tabs, Tree, Badge, Banner, and a braille line chart. Use when an agent needs a dashboard, panel, table, status page, TUI layout, tmux dashboard, screenshot-driven CLI/TUI replica, ANSI frame, truecolor render, pyte PNG screenshot smoke test, wide-character alignment, or a new terminal widget.
allowed-tools
Bash, Read
version
0.1.0
# tui-ui Lay out a terminal screen the way you lay out a web page: nested boxes with margin, border, and padding; rows and columns that flex with `fr` units; and drop-in widgets (panels, tables, cards, meters, tabs, trees). You compose a tree of renderables, and the engine resolves sizes, composites cell grids, and serializes **once** to a tmux-safe ANSI frame. Everything is display-cell accurate, so CJK, emoji, and box-drawing never desync your columns. Pure Python **stdlib** (optional `pyfiglet` for the Banner, optional `wcwidth` for an authoritative width — a stdlib fallback covers the same edge cases). Output is a plain string you can `print`, pipe, feed to the fx play loop, or render to PNG with the SmartCLI pyte harness. ## When to use - Building a status page / dashboard / control panel that should look designed, not dumped. - You need boxes, tables, or columns that **line up** — especially with wide glyphs. - You want web mental models (box model, flexbox rows, a grid, cards) in a TUI. - Adding a new reusable terminal widget to a shared catalog. - Do NOT use for full-screen *interactive* apps with input loops — that is drive-tui's job. tui-ui produces *frames*; something else owns the terminal. ## Setup Run from the skill directory `skills/tui-ui` as a package: ``` python -m ui widgets # list widgets python -m ui demo <name> --width 60 --height 12 # render one widget python -m ui gallery --width 100 --height 30 # the showcase dashboard "page" python -m ui demo table --theme synthwave # --theme applies a palette ``` Or by path from anywhere (a PEP-366 prelude bootstraps the package): `python skills/tui-ui/ui/cli.py gallery` On Windows set `PYTHONIOENCODING=utf-8` (or the CLI auto-reconfigures stdout) so box/CJK glyphs encode. The CLI renders **once** and exits — bounded, no loop. ## The box model (`ui/box.py`) A `Box` wraps content in the CSS nesting order **margin → border → padding → content**. Borders are *cells* (1 per present side), padding is the CSS 4-tuple `(top, right, bottom, left)`, and `box-sizing` defaults to **border-box**: the requested `width`/`height` IS the border box, and `content = width − gutter` where `gutter = padding + border` per axis. ```python from ui import Box Box(content="hello", width=24, height=5, # int cells | "auto" | Fr(2)/"2fr" | "50%" border="rounded", # single | rounded | heavy | double | ascii | none padding=(0, 1), # int | (v,h) | (t,r,b,l) margin=0, title="Title", title_align="left", # left | center | right align="left", valign="top", # content alignment inside the padding box fg=(210,216,228), bg="#0e1016", border_fg="#4a5a82") ``` `Box.measure(w, h) -> (w, h)` reports the natural outer size; `Box.render(w, h) -> Canvas` draws it into an exact region. Content taller/wider than the region is **clipped** to the content box so it never overwrites the border. ## Layout (`ui/layout.py`) Containers share the widget protocol (`measure` / `render`), so they nest freely. - `VStack(children, gap, align, valign)` — stack top→bottom; cross axis is width. - `HStack(children, gap, valign)` — flex row left→right; `fr` children share width. - `Grid(cells, cols, rows, col_gap, row_gap)` — rows × cols; `grid(cells, ncols=3)` is the shortcut. - `Page(child, width, height)` — the fixed-size root; `page.to_ansi()` is your frame. **fr / fractional distribution** is the load-bearing algorithm. Never round each track independently (that drifts ±1). The engine uses Rich's carry-remainder method, so `resolve_tracks(10, [Fr(1),Fr(1),Fr(1)], ...) == [3, 3, 4]` exactly and cumulative offsets never drift. Fixed/auto tracks are measured and subtracted first; the remainder is shared among `fr` tracks by weight; overflowing fixed tracks shrink largest-first to fit. ## Compose a "page" A dashboard is just a tree of boxes and widgets handed to a `Page`: ```python from ui import Page, VStack, HStack, Box, Fr, get_theme from ui.widgets import Banner, Badge, Table, Meter, ProgressBar, Tabs, Tree t = get_theme("dashboard") def panel(content, title, **kw): return Box(content, border="single", title=title, padding=(0,1), bg=t.bg, fg=t.fg, border_fg=t.border, **kw) page = Page( VStack([ Box(Banner("DASH", theme=t), border="rounded", border_fg=t.accent, height=8), HStack([panel(Meter(theme=t), "Resources"), # auto width panel(Table(theme=t), "Services", width=Fr(1))], gap=1), # flex fill panel(ProgressBar(0.73, label="Deploy", theme=t), "Progress", height=3), ], gap=0, bg=t.bg), width=100, height=30, bg=t.bg) print(page.to_ansi()) ``` See `ui/cli.py:_build_gallery` for the full showcase (banner, badge status row, a two-panel middle region, tabs, and a progress footer). Themes live in `ui/core.py` (`dashboard`, `synthwave`, `forest`, `mono`, `amber`); each carries semantic slots (`bg fg muted border accent ok warn err`) plus gradient `stops`. ## Widget catalog `python -m ui widgets` lists them; each has a `sample(theme)` used by `demo`/`gallery`. | key | what it renders | |---|---| | `panel` | Bordered frame with title + word-wrapped body | | `table` | Auto-sized data table with borders, bold header, status coloring | | `card` | Composite: title + body + key/value list + badge footer | | `progress` | Progress bar, 1/8-cell precision (`█▉▊▋▌▍▎▏`), gradient fill + percent | | `meter` | Multi-row labelled bar chart (CPU/MEM/DISK style) | | `tabs` | Tab strip with the active tab underlined + a content pane | | `kv` | Two-column key/value (definition) list; keys padded, values wrap | | `tree` | DFS tree with `├──`/`└──`/`│` guide glyphs | | `rule` | Horizontal divider with an optional aligned title | | `badge` | Inline status pill / chip (`ok`/`warn`/`err`/`accent`) | | `banner` | Big FIGlet text (needs `pyfiglet`; degrades to a bold label) | Plus 4 shader/raster-backed extras shipped in `ui/widgets_ext/` (the effort-replica primitives + a sub-cell chart; also in `python -m ui widgets`): | key | what it renders | |---|---| | `gradient_rule` | Solid rule filled with a per-cell truecolor gradient | | `radial_glow` | Localized rounded/pulsing radial glow (background light field) | | `slider_track` | Thin solid slider rail with a marker + tick labels | | `braille_chart` | Smooth sub-cell line chart of a data series (braille 2×4 px/cell) | ## Add a widget (the recipe) Drop a module in `ui/widgets_ext/` — `registry.load_all()` discovers it, no wiring. ```python # ui/widgets_ext/spark.py from ui.registry import register from ui.widgets import Widget from ui.core import Canvas @register class Spark(Widget): key = "spark" # registry key (python -m ui demo spark) summary = "Inline sparkline" # shown by `widgets` def __init__(self, values=(1,3,2,5,4), *, theme=None): super().__init__(theme) self.values = list(values) def measure(self, avail_w, avail_h): # (width_wanted, height_wanted) return (len(self.values), 1) def render(self, region_w, region_h): # -> exact-size Canvas cv = Canvas(region_w, max(1, region_h), bg=self.theme.bg) bars = "▁▂▃▄▅▆▇█"; hi = max(self.values) or 1 for i, v in enumerate(self.values[:region_w]): cv.set(i, 0, bars[min(7, v*7//hi)], fg=self.theme.accent, bg=self.theme.bg) return cv @classmethod def sample(cls, theme): # used by demo/gallery return cls(theme=theme) ``` Contract every widget honors: `measure(avail_w, avail_h) -> (w, h)` and `render(region_w, region_h) -> Canvas` of exactly that region. Set class attrs `width`/`height` (int / `Fr` / `"auto"`) if a parent stack should size it specially. Then: `python -m ui widgets` (it appears) and `python -m ui demo spark`. ## Width & alignment (why columns don't misalign) The engine never uses `len()`. `ui.core.width(s)` returns **display cells**: CJK / fullwidth → 2, combining marks → 0, ANSI stripped to 0, and the emoji edge cases handled — ZWJ sequences (`👩‍💻`=2), VS16 (`♀️`=2), regional-indicator flag pairs (`🇯🇵`=2). `Canvas.put_text` is wide-aware: a double-width glyph occupies its cell plus a continuation cell that is never serialized, so a CJK char can't shove the columns behind it. `blit` heals any wide glyph it cuts at a seam. ## tmux safety & rendering to PNG `to_ansi()` emits only CSI **SGR** color runs (`\x1b[…m`, run-length like fx) and newlines — no cursor moves, no alt-screen, no scroll region. A frame is therefore composable and safe to print inside a tmux pane; tmux re-parses it through its own VT layer identically. There is **no real tmux/docker/WSL on this box**, so rendering is verified with **pyte** (a faithful VT emulator) → **PNG** via PIL — the standard no-tmux snapshot method. Always label such captures as pyte-simulation, not a real-tmux capture (see `tools/screenshot/shot.py:RENDER_LABEL`). ```python import sys; sys.path[:0] = ["tools/screenshot", "skills/tui-ui"] import shot from ui import get_theme; from ui.cli import _build_gallery ansi = _build_gallery(get_theme("dashboard"), 100, 30).to_ansi() screen = shot.render_bytes_to_screen(shot.render_frame_to_bytes(ansi), 100, 30) shot.screen_to_png(screen, "out.png") # faithful cell-grid render ``` ## Knowledge base — look before you build Before recreating or inventing any effect, consult the SmartCLI knowledge graph at `D:/Project/SmartCLI/knowledge/INDEX.md` — 89 sourced concept notes + 27 case studies, organized around one discipline: **pick your lane.** - **Replica** (reproduce a real, existing UI/animation/screenshot): measure ground truth first, never head-canon. Start at [[hard-lessons]] (mirrors `references/HARD-LESSONS.md`) and the worked case [[effort-selector]] (measured palette/geometry/animation for the `/effort` picker) → drive-and-capture, quantify scale + form, extract exact params, build against truth, verify on the real run path. - **Creative** (design a new widget/effect): compose primitives. Start at [[rendering-model]] (mirrors `references/RENDERING-MODEL.md`, the four-primitive kernel) and ask "which primitives compose this?" before writing a bespoke widget. ## Recreating a REAL effect — read this first Before recreating any effect that already exists (a real CLI's UI, an animation, a screenshot), **read `references/HARD-LESSONS.md`** and follow its 10 rules. It is the distilled record of a replica that took a dozen wrong iterations because of guessing instead of measuring. The non-negotiable core: 1. **Get ground truth first — never approximate from imagination.** If the real program exists, drive it with `smartcli_core.PtySession`, capture the actual rendered per-cell bytes/colors, and for animations capture **multiple PNG frames** and study them. Reverse-engineer exact constants from source when available. 2. **Confirm scale and shape before writing render code** — how many rows/cols the effect occupies, 1-D vs 2-D, static vs animated, and *how* it moves (measure the moving edge across frames, don't guess). A ripple that is really an 8-row rectangle will never be reproduced as a 1-row bar. 3. **Verify on the REAL run path, never on a self-satisfying preview.** Run the script's own full startup (`python script.py`, no monkeypatches), capture stderr, and use SmartCLI to drive *your own script* and diff its real output against ground truth. A missing import or an `isatty()` early-return produces a blank/crash the PNG preview won't show. 4. **You have the tools to look — use them.** You are an agent CLI with PTY + screen capture + PNG rendering. Don't ask the user what it looks like; go capture it. ## Replica smoke workflow When recreating a screenshot-driven CLI/TUI, do not finish after visual intuition or one happy-path preview. Create a bounded `--once` frame mode in the target program, then render a matrix through `tools/screenshot/shot.py` or `tools/screenshot/cli.py`. Minimum checks for a screenshot replica: - Cover every visible state in the reference, plus separate animation frames for each distinct animated backdrop or transition shape. - Render at the reference size and at least two stress sizes, such as `80x24` and a wider/taller terminal. - Assert the stream contains truecolor ANSI when color is part of the design, has no `U+FFFD`, and includes the key visible labels from the reference. - Inspect the generated PNG contact set manually before handoff; pyte checks catch blanks, clipping, color presence, and mojibake, but not taste, exact spacing, or whether an active color reads like the reference. - Run one real Windows Terminal interactive smoke for input semantics, alt-screen restoration, cursor restoration, and resize behavior. Pyte is not a substitute for keyboard/input-loop verification. For interactive replicas, make `Enter` and `Esc` behavior observable: emit the selected value or use distinct exit status/cancel semantics after leaving the alt screen. Keep `--once` non-interactive and deterministic so reports can reproduce the PNG set. ## Self-test (bounded, no loop) `python self_test.py` renders the 100×30 dashboard once and asserts: 30 rows each exactly 100 cells (no fr drift), box-drawing glyphs present, truecolor SGR present, `width()` edge cases correct, and a CJK table row's vertical bars land on the SAME columns as an ASCII row (proving wide-char alignment). Exit 0 = pass. It also passes cleanly at sizes (40,12), (80,24), (120,40), (200,50). ## File map - `ui/core.py` — `Canvas`, `Cell`, `width()`, color/SGR helpers, box-glyph tables, `Theme`s. - `ui/box.py` — the CSS box model (`Box`, `Fr`, dim resolution, clipping). - `ui/layout.py` — `VStack`/`HStack`/`Grid`/`Page` + carry-remainder `fr` resolution. - `ui/widgets.py` — the 11 core web-style widgets. - `ui/widgets_ext/` — 4 shipped extras (`gradient_rule`, `radial_glow`, `slider_track`, `braille_chart`) + drop a module here to add your own. - `ui/registry.py` — `@register` + folder discovery. - `ui/cli.py` — `widgets` / `demo` / `gallery` (+ `--width --height --theme`). - `ui/field.py` — CellField shader engine (`Ripple`/`RadialGlow`/`LinearGradient`/`Plasma` + ASPECT dist). - `self_test.py` — bounded render assertions.
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看