本文へ移動
cccskills
無料GitHub で公開

tui-design

Design specification for CLI TUI (Terminal User Interface). This skill provides comprehensive guidelines for implementing interactive terminal UI components, including page layout structure, color schemes, keyboard navigation, and multi-level navigation principles.

インストール方法を見る

含まれるファイル(2)

  • SKILL.md19.8 KB
  • LICENSE.txt1.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

CLI TUI Design Specification

This document provides comprehensive design guidelines for implementing interactive terminal user interface (TUI) applications.

When to Use This Skill

  • Developing or modifying CLI TUI components
  • Adding new UI pages or interactions
  • Implementing new visual styles or themes
  • Creating similar TUI applications following this pattern

1. Page Layout Structure

Every TUI page follows a consistent four-section layout:

┌────────────────────────────────────────────────────────────────┐
│  Header: Title Line + Separator + ASCII Logo + Separator       │
├────────────────────────────────────────────────────────────────┤
│  Info Bar: Context-dependent information (Dynamic)             │
├────────────────────────────────────────────────────────────────┤
│  Content Area: Interactive options/list (Scrollable)           │
│                                                                │
├────────────────────────────────────────────────────────────────┤
│  Footer: Status Bar + Separator + Keyboard Hints               │
└────────────────────────────────────────────────────────────────┘

Section Details

1.1 Header Section (Always Visible)

The header consists of a title line, separator, ASCII logo, and separator with symmetric spacing:

🚀 Skills-X - AI Agent Skills Manager  v0.2.12
────────────────────────────────────────────────────────────
                                                  <- 1 blank line (from logo constant leading \n)
   ███████╗██╗  ██╗██╗██╗     ...
   ╚══════╝╚═╝  ╚═╝╚═╝╚══════╝...
                                                  <- 1 blank line (from logo constant trailing \n + 1 extra \n)
────────────────────────────────────────────────────────────

Spacing rules:

  • The logo constant uses backtick string, which naturally includes a leading \n (after opening backtick) and trailing \n (before closing backtick)
  • After logoStyle.Render(logo), add exactly one \n — this produces symmetric 1-blank-line padding on both sides
  • Never add \n\n after logo — it creates asymmetric bottom padding
func RenderLogo(version string) string {
    var b strings.Builder
    // Title + version (strip -dirty suffix for display)
    b.WriteString(accentStyle.Render("🚀 Skills-X - AI Agent Skills Manager"))
    if version != "" {
        displayVersion := strings.TrimSuffix(version, "-dirty")
        b.WriteString("  " + hintStyle.Render(displayVersion))
    }
    b.WriteString("\n")
    // Upper separator
    b.WriteString(separatorStyle.Render(strings.Repeat("─", SeparatorWidth)))
    b.WriteString("\n")
    // Logo (constant has leading \n and trailing \n built-in)
    b.WriteString(logoStyle.Render(logo))
    b.WriteString("\n")  // exactly 1 \n — symmetric with leading \n
    // Lower separator
    b.WriteString(separatorStyle.Render(strings.Repeat("─", SeparatorWidth)))
    b.WriteString("\n")
    return b.String()
}

Newline budget between header and content:

  • RenderLogo ends with separator + \n
  • View should not add extra \n after RenderLogo() — content starts immediately

1.2 Info Bar Section (Dynamic)

  • Page-specific context: table headers, counts, search box
  • Context info uses dim/hint color
  • No working directory display on LV1 — only shown on pages where it adds value (e.g. LV3 skills list)

1.3 Content Area (Interactive)

  • Table View: Column headers + data rows with visual-width alignment
  • List View: Scrollable list with checkbox multi-selection
  • Progress View: Progress bar with percentage and counts

Key features:

  • Visual-width-aware column alignment (see Section 3)
  • Numeric columns right-aligned with fmt.Sprintf("%4d")
  • Checkbox (☐/☑) for multi-selection
  • Cursor indicator (❯) for current position
  • Empty line padding to maintain stable page height

1.4 Footer Section

(共 30 个,已安装 12 个,将安装 3 个,将卸载 1 个)

────────────────────────────────────────────────────────────
/ 搜索 | 空格 选择/反选 | A 全选 | Enter 确认安装/卸载 | b 返回 | q 退出

Composition:

  1. Status bar: \n + counts in dim color, parenthesized
  2. Separator: \n + 60-char ─ line + \n
  3. Hints: Keyboard shortcuts in dim color, pipe-separated
func RenderStatusBar(total, installed, newSelected, deselected int) string {
    return "\n" + hintStyle.Render(
        fmt.Sprintf("(共 %d 个,已安装 %d 个,将安装 %d 个,将卸载 %d 个)",
            total, installed, newSelected, deselected))
}

func RenderHint(hint string) string {
    return "\n" + RenderSeparator() + hintStyle.Render(hint)
}

func RenderSeparator() string {
    return separatorStyle.Render(strings.Repeat("─", SeparatorWidth)) + "\n"
}

2. Style Definitions

Color Palette

Color NameHex CodeVariableUsage
Primary#00D4AAprimaryColorSelected items, logo
Secondary#FF6B6BsecondaryColorCursor, errors, alerts
Accent#4ECDC4accentColorTitle line, highlights
Dim#666666dimColorHints, separators
White#FFFFFFwhiteColorSection titles, headers
Yellow#FFCC00yellowColorWarnings (reserved)
Blue#5599FFblueColorLinks (reserved)
Cyan#5A9FB8cyanColorSelectable items

Component Styles

logoStyle       = lipgloss.NewStyle().Foreground(primaryColor).Bold(true)
titleStyle      = lipgloss.NewStyle().Foreground(whiteColor).Bold(true)
selectedStyle   = lipgloss.NewStyle().Foreground(primaryColor).Bold(true)
normalStyle     = lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA"))
selectableStyle = lipgloss.NewStyle().Foreground(cyanColor)
hintStyle       = lipgloss.NewStyle().Foreground(dimColor)
cursorStyle     = lipgloss.NewStyle().Foreground(secondaryColor).Bold(true)
successStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00"))
separatorStyle  = lipgloss.NewStyle().Foreground(dimColor)
searchStyle     = lipgloss.NewStyle().Foreground(whiteColor).Background(lipgloss.Color("#333333"))

Status Indicators

SymbolMeaningStyle
☑SelectedselectedStyle
☐Not selected—
❯Cursor positioncursorStyle

3. Column Alignment & Visual Width

The Problem

fmt.Sprintf width specifiers count bytes, not terminal columns. This breaks alignment when:

  • ANSI escape codes (lipgloss styles) add invisible bytes
  • CJK characters (中文、日本語) occupy 2 terminal columns per rune

The Solution: Pad First, Style After

Rule: Always pad plain text to the target visual width, then apply lipgloss style.

// termWidth returns the visual width of a string in terminal columns.
func termWidth(s string) int {
    w := 0
    for _, r := range s {
        if isCJK(r) {
            w += 2
        } else {
            w++
        }
    }
    return w
}

// padRight pads a string with spaces to reach the target visual width.
func padRight(s string, targetWidth int) string {
    w := termWidth(s)
    for w < targetWidth {
        s += " "
        w++
    }
    return s
}

Usage Pattern

// Header: pad plain text, then style
headerName := padRight("AI 工具", 22)   // "AI 工具" = 7 visual cols → 15 spaces added
b.WriteString(fmt.Sprintf("  %s  %s  %s\n",
    titleStyle.Render(headerName),           // styled AFTER padding
    titleStyle.Render(padRight("全局", 4)),   // "全局" = 4 visual cols → 0 spaces
    titleStyle.Render(padRight("项目", 4))))

// Data rows: same column widths
name := padRight(p.Name, 22)             // "Claude Code" = 11 cols → 11 spaces
globalStr := fmt.Sprintf("%4d", count)   // right-aligned number, 4 chars
b.WriteString(fmt.Sprintf("%s%s  %s  %s\n",
    prefix,                               // "  " or "❯ " (always 2 visual cols)
    style.Render(name),                   // styled AFTER padding
    globalCountStyle.Render(globalStr),   // styled AFTER formatting
    projectCountStyle.Render(projectStr)))

Column Width Standards

ColumnVisual WidthAlignmentPadding Method
Name22LeftpadRight(text, 22)
Skill name40LeftpadRight(text, 40)
Number4Rightfmt.Sprintf("%4d", n)
Status (✓/✗)1CenterDirect render
Prefix (❯)2Left" " or cursorStyle("❯ ")

Gap Between Columns

Use 2 spaces (" ") between columns in fmt.Sprintf:

fmt.Sprintf("%s%s  %s  %s\n", prefix, name, col1, col2)
//                ^^    ^^  two-space gaps

4. Separator Rules

All separators use the same constant width:

const SeparatorWidth = 60
LocationCharacterWidthColor
Above ASCII logo─60dimColor
Below ASCII logo─60dimColor
Below table header─60dimColor
Above footer hints─60dimColor

All separators use SeparatorWidth (60). Never hardcode different widths.


5. Keyboard Navigation

Universal Keys (All Levels)

KeyAction
qQuit application
Ctrl+CForce quit

List Navigation

KeyAction
↑Move cursor up (wrap to bottom)
↓Move cursor down (wrap to top)
PgUpPage up
PgDownPage down
HomeJump to first item
EndJump to last item

Selection

KeyAction
SpaceToggle selection (cursor stays)
a / ASelect all visible items
EnterConfirm selection

Multi-Level Navigation

KeyAction
Esc / bGo back to previous level
EnterProceed to next level

Search Mode

KeyAction
/ / Ctrl+FEnter search mode
TypingAppend to search query
BackspaceRemove last character
EscExit search mode, clear query

6. Multi-Level Page Architecture

Page Flow

Level 1           Level 2             Level 3            Level 4
  │                  │                   │                  │
  ▼                  ▼                   ▼                  ▼
Select ────────► Select ──────────► Select ──────────► Install
AI Tool          Target (global/     Skills (multi-     Progress
                 project)            select + search)

Navigation Rules

  1. Forward: Enter confirms and proceeds to next level
  2. Backward: Esc or b returns to previous level (restarts the loop)
  3. Quit: q or Ctrl+C exits at any level
  4. State preservation: Going back re-runs the previous level from scratch

Orchestration Pattern

Alt screen is managed once at the top level to avoid flicker between pages:

func RunTUI(opts TUIOptions) error {
    // Enter alt screen ONCE for the entire TUI session
    fmt.Print(EnterAltScreen)
    fmt.Print(HideCursor)
    defer func() {
        fmt.Print(ShowCursor)
        fmt.Print(ExitAltScreen)
    }()
    return runTUIFlow(opts)
}

func runTUIFlow(opts TUIOptions) error {
    // Level 1
    fmt.Print(ClearScreen)
    product, err := RunProductSelect(opts.Version, opts.TargetDir)
    if err != nil || product == nil {
        return err
    }

    // Level 2
    fmt.Print(ClearScreen)
    target, err := RunInstallTargetSelect(product, opts.TargetDir)
    ...

    // Level 3
    fmt.Print(ClearScreen)
    selected, deselected, err := RunSkillsSelect(...)
    if err != nil && err.Error() == "go back" {
        return runTUIFlow(opts)  // recursive restart
    }

    // Level 4
    fmt.Print(ClearScreen)
    RunInstaller(selected, deselected, targetDir)

    // Exit alt screen to show final result on normal terminal
    fmt.Print(ShowCursor)
    fmt.Print(ExitAltScreen)
    fmt.Printf("Completed: %d\n", completed)
    return nil
}

7. Terminal Control

Two-Layer Rendering Strategy

The rendering uses a two-layer approach to eliminate flicker:

Layer 1: Alt screen lifecycle (managed by RunTUI)

  • Enter alt screen once at the start, exit once at the end
  • ClearScreen between each page transition (within the alt screen buffer)
  • Never use tea.WithAltScreen() on individual programs — it causes exit/enter cycles that flash the normal terminal

Layer 2: Per-page rendering (managed by Bubble Tea)

  • Each page runs as tea.NewProgram(model) (no WithAltScreen)
  • Bubble Tea handles incremental rendering within the page
  • View() returns complete page content; never include ClearScreen in View()
// Individual programs: NO WithAltScreen
p := tea.NewProgram(model)  // Bubble Tea renders incrementally

// Page transitions: ClearScreen within the shared alt screen
fmt.Print(ClearScreen)      // clears alt screen buffer between pages

Anti-patterns:

  • tea.NewProgram(model, tea.WithAltScreen()) per level → flicker on every page switch
  • ClearScreen inside View() → full repaint on every keystroke
  • fmt.Printf(...) between levels while in alt screen → cursor position corruption

ANSI Sequences

const ClearScreen    = "\033[2J\033[H"    // Clear screen + cursor home
const EnterAltScreen = "\033[?1049h"      // Enter alternate screen buffer
const ExitAltScreen  = "\033[?1049l"      // Exit alternate screen buffer
const HideCursor     = "\033[?25l"        // Hide cursor
const ShowCursor     = "\033[?25h"        // Show cursor

8. Layout Examples

Level 1: Product Selection

🚀 Skills-X - AI Agent Skills Manager  v0.2.12
────────────────────────────────────────────────────────────

   ███████╗██╗  ██╗██╗██╗     ██╗     ███████╗     ██╗  ██╗
   ...
   ╚══════╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝╚══════╝     ╚═╝  ╚═╝

────────────────────────────────────────────────────────────
  AI 工具                 全局  项目
────────────────────────────────────────────────────────────
❯ Claude Code               45     0
  Cursor                      0    36
  Windsurf                    0     0

────────────────────────────────────────────────────────────
↑↓ 选择 | Enter 确定 | q 退出

Level 3: Skills Selection

🚀 Skills-X - AI Agent Skills Manager  v0.2.12
────────────────────────────────────────────────────────────

   ███████╗...
   ╚══════╝...

────────────────────────────────────────────────────────────

选择要安装的 Skills  Cursor
📁 /path/to/project

 🔍 搜索技能...
                                          已安装
────────────────────────────────────────────────────────────
❯ ☑ anthropic/brainstorming                ✓
  ☑ anthropic/doc-coauthoring              ✓
  ☐ anthropic/frontend-design              ✗
  ...

↑ 3/30 ↓

(共 30 个,已安装 12 个,将安装 3 个,将卸载 1 个)

────────────────────────────────────────────────────────────
/ 搜索 | 空格 选择/反选 | A 全选 | Enter 确认安装/卸载 | b 返回 | q 退出

Level 4: Installation Progress

Installing Skills

Progress: [========================================] 100% (9/9)
Completed: 9 | Failed: 0

To uninstall:
   [OK] anthropic/doc-coauthoring
   [OK] anthropic/frontend-design

────────────────────────────────────────────────────────────
Done! 9 completed.

9. Constants

const SeparatorWidth  = 60  // All separators use this width
const DefaultPageSize = 10  // Default items per page in scrollable lists

10. Design Principles Summary

  1. Consistent Layout: Every page has Header → Info Bar → Content → Footer
  2. Uniform Separators: All ─ lines use SeparatorWidth (60), never hardcode other values
  3. Symmetric Spacing: Logo has equal blank-line padding above and below
  4. Visual-Width Alignment: Use padRight/termWidth for CJK-aware column alignment; pad plain text first, style after
  5. Flicker-Free: Manage alt screen at RunTUI level (enter once, exit once), use ClearScreen between pages, never ClearScreen in View()
  6. Stable UI: Fixed column widths, empty line fill, scroll offset tracking
  7. Multi-Level Flow: Independent programs per level, back/forward navigation

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

百度语音识别 - 将语音消息转换为文本。支持中文普通话、英语、粤语、四川话。专为国内服务器环境优化,自动绕过代理访问百度 API。

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

Use when you have a written implementation plan to execute in a separate session with review checkpoints

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

帮助用户将飞书群机器人 Webhook 通知接入 Claude Code,使得每次任务完成后自动发送飞书消息卡片通知。 触发场景(当用户提到以下任意内容时激活此 skill): - "飞书通知"、"接入飞书"、"飞书提醒" - "task 完成通知"、"任务完成提醒" - "claude 完成提醒"、"claude 通知" - "Stop hook 通知"、"hook 通知" - "发飞书消息"、"飞书机器人" - "任务结束通知"、"配置通知"

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

日本語の概要は準備中です。原文の説明を表示しています。

castle-x/skills-x182026年4月16日 更新

castle-x のスキルをすべて見る

このスキルの問題を報告する