building-glamorous-tuis について
Charmbracelet のライブラリでターミナル UI を構築するためのリファレンススキルです。シェルスクリプトの入力、Go TUI、SSH 上の UI、実運用向けの設計パターンを扱います。
主な機能
- 01ライブラリ選定ガイド
- 02Gum によるシェルスクリプト入力
- 03Bubble Tea と Lip Gloss による Go TUI
- 04SSH 対応 TUI のパターン
- 05実運用向け設計と最適化
- 06レイアウト、フォーム、アニメーション、テスト
活用シーン
- 01シェルスクリプトや Go TUI のライブラリ選定
- 02スクリプトへの対話的な入力や選択の追加
- 03レスポンシブな複数画面のターミナル UI 構築
- 04TUI の性能と信頼性の改善
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dicklesworthstone/ meta_skill --skill building-glamorous-tuisインストール後、Claude Code で/building-glamorous-tuisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-patterns.mdcomponent-catalog.mdgo-tui.mdinfrastructure.mdproduction-architecture.mdPROMPTS.mdQUICK-REFERENCE.mdshell-scripts.md詳細
Charmbracelet で魅力的な TUI を構築する
クイックルーター — まずはこちら
| やりたいこと | 使用するもの | リファレンス |
|---|---|---|
| シェルスクリプトにプロンプトやスピナーを追加する | Gum(Go 不要) | シェルスクリプト |
| Go の TUI を構築する | Bubble Tea + Lip Gloss | Go TUI |
| 本番環境向けの Go TUI を構築する | 上記 + 高度なパターン | 本番環境向けアーキテクチャ |
| SSH 経由で TUI を提供する | Wish + Bubble Tea | インフラストラクチャ |
| ターミナルのデモを録画する | VHS | シェルスクリプト |
| Bubbles のコンポーネントを探す | list、table、viewport、spinner、progress... | コンポーネントカタログ |
| コピーして使えるパターンを見つける | レイアウト、フォーム、アニメーション、テスト | クイックリファレンス / 高度なパターン |
選択ガイド
Is it a shell script?
├─ Yes → Use Gum
│ Need recording? → VHS
│ Need AI? → Mods
│
└─ No (Go application)
│
├─ Just styled output? → Lip Gloss only
├─ Simple prompts/forms? → Huh standalone
├─ Full interactive TUI? → Bubble Tea + Bubbles + Lip Gloss
│ │
│ └─ Production-grade? → Also add elite patterns:
│ (multi-view, data- two-phase async, immutable snapshots,
│ dense, must be adaptive layout, focus state machine,
│ fast & polished) semantic theming, pre-computed styles
│ → See Production Architecture reference
│
└─ Need SSH access? → Wish + Bubble Tea
シェルスクリプト(Go 不要)
brew install gum # One-time install
# Input
NAME=$(gum input --placeholder "Your name")
# Selection
COLOR=$(gum choose "red" "green" "blue")
# Fuzzy filter from stdin
BRANCH=$(git branch | gum filter)
# Confirmation
gum confirm "Continue?" && echo "yes"
# Spinner
gum spin --title "Working..." -- long-command
# Styled output
gum style --border rounded --padding "1 2" "Hello"
Gum の完全ガイド → VHS の録画 → Mods AI →
Go アプリケーション
go get github.com/charmbracelet/bubbletea github.com/charmbracelet/lipgloss
最小限の TUI(コピーして実行)
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var highlight = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true)
type model struct {
items []string
cursor int
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
case "up", "k":
if m.cursor > 0 { m.cursor-- }
case "down", "j":
if m.cursor < len(m.items)-1 { m.cursor++ }
case "enter":
fmt.Printf("Selected: %s\n", m.items[m.cursor])
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
s := ""
for i, item := range m.items {
if i == m.cursor {
s += highlight.Render("▸ "+item) + "\n"
} else {
s += " " + item + "\n"
}
}
return s + "\n(↑/↓ move, enter select, q quit)"
}
func main() {
m := model{items: []string{"Option A", "Option B", "Option C"}}
tea.NewProgram(m).Run()
}
ライブラリ早見表
| 必要なもの | ライブラリ | 例 |
|---|---|---|
| TUI フレームワーク | bubbletea | tea.NewProgram(model).Run() |
| コンポーネント | bubbles | list.New()、textinput.New() |
| スタイル設定 | lipgloss | style.Foreground(lipgloss.Color("212")) |
| フォーム | huh | huh.NewInput().Title("Name").Run() |
| Markdown | glamour | glamour.Render(md, "dark") |
| アニメーション | harmonica | harmonica.NewSpring() |
Go TUI の完全ガイド → Bubbles の全コンポーネント → レイアウトとアニメーションのパターン →
SSH アプリケーション(インフラストラクチャ)
s, _ := wish.NewServer(
wish.WithAddress(":2222"),
wish.WithHostKeyPath(".ssh/key"),
wish.WithMiddleware(
bubbletea.Middleware(handler),
logging.Middleware(),
),
)
s.ListenAndServe()
接続:ssh localhost -p 2222
本番環境向け TUI アーキテクチャ(高度なパターン)
基本的な Bubble Tea の使い方にとどまらず、TUI を高速で洗練されたプロフェッショナルな仕上がりにするパターンを紹介します。 各項目から、本番環境向けアーキテクチャの完全なコード例に移動できます。
TUI の動作が遅い、またはカクつく
| 症状 | パターン | 対処法 |
|---|---|---|
| 計算中に UI がブロックされる | 2 フェーズ非同期処理 | フェーズ 1 は即時に実行し、フェーズ 2 はバックグラウンドの goroutine で実行 |
| 描画処理が mutex を保持する | 不変スナップショット | スナップショットを事前に構築し、アトミックなポインター交換を行う |
| ファイルの変更でカクつく | バックグラウンドワーカー | デバウンス付きウォッチャー + イベントの集約 |
| フレームごとに数千回のメモリ割り当てが発生する | スタイルの事前計算 | 起動時にデリゲートのスタイルを一度だけ割り当てる |
| View() で O(n²) の文字列連結が発生する | strings.Builder | Grow() を使って Builder の容量を事前に確保する |
| Glamour がフレームごとに再描画される | Markdown のキャッシュ | コンテンツハッシュでキャッシュし、幅の変更時に無効化する |
| 操作中に GC が一時停止する | アイドル時間の GC | アイドル時間に GC を実行する |
| 大規模なデータセットでメモリ使用量が増える | オブジェクトプーリング | 事前割り当てしたスライスと sync.Pool を使う |
| 画面外の項目まで描画される | ビューポートの仮想化 | 表示中の行のみ描画する |
端末によってレイアウトが崩れます
| 症状 | パターン | 解決策 |
|---|---|---|
| 固定幅が崩れる | アダプティブレイアウト | 3〜4 段階のレスポンシブブレークポイント(80/100/140/180 列) |
| 明るい背景の端末で色がおかしい | セマンティックなテーマ設定 | lipgloss.AdaptiveColor + WCAG AA のコントラスト |
| 項目の優先度が同じで、リストの順序が変わる | 決定的な並べ替え | 第 2 キーで同順位を解決する安定ソート |
| ソートモードが見えない | 動的ステータスバー | 左右のセグメントと余白の自動補完 |
| 更新する項目がありません | 決定的な並べ替え | 第 2 キーで同順位を解決する安定ソート |
TUI に複数のビューがあり、構成が複雑になっています
| 症状 | パターン | 解決策 |
|---|---|---|
| キーのルーティングが混乱する | フォーカス状態マシン | 明示的なフォーカス列挙型 + モーダルの優先レイヤー |
| ネストしたビューでユーザーが迷う | パンくずナビゲーション | Home > Board > Priority のパス表示 |
| オーバーレイを閉じると位置が失われる | フォーカスの復元 | オーバーレイ表示前にフォーカスを保存し、閉じる際に復元 |
| 古い非同期結果が新しいデータを上書きする | 古いメッセージの検出 | 結果を適用する前にデータハッシュを比較 |
| フレームごとに複数のコンポーネントを更新する | tea.Batch の蓄積 | cmds をスライスに集め、tea.Batch(cmds...) を返す |
| バックグラウンドのゴルーチンでパニックが発生すると TUI が停止する | エラーリカバリー | すべてのゴルーチンに defer/recover ラッパーを適用 |
データ豊富な可視化を追加したい
| 要望 | パターン | コード |
|---|---|---|
| リスト列に棒グラフを表示する | Unicode スパークライン | 8 段階のブロック文字を使った ▇▅▂ |
| 強度に応じて色を付ける | 知覚ベースのヒートマップ | グレー → ブルー → パープル → ピンクのグラデーション |
| 端末に依存関係グラフを表示する | ASCII グラフレンダラー | キャンバス + マンハッタン配線(╭─╮│╰╯) |
| 経過時間を一目で把握する | 経過時間のカラーコード化 | 新しい=緑、経過中=黄、古い=赤 |
| 意味のある境界線を使う | セマンティックな境界線 | 赤=ブロック中、緑=準備完了、黄=影響大 |
TUIを洗練されたプロフェッショナルな見た目にしたい
| 要望 | パターン | 主なアイデア |
|---|---|---|
Vim風のgg/G | Vimキーの組み合わせ | キー入力の間、waitingForG状態を追跡します |
| もたつかない検索 | デバウンス検索 | 150msのタイマーを使い、入力が止まったときだけ実行します |
| すべてのフィールドを一度に検索 | 複合FilterValue | すべてのフィールドを1つの文字列にまとめます |
| メタデータ付きの4行カード | リッチデリゲート | Height()=4のカスタムデリゲートを使います |
| 詳細をインラインで展開 | インライン展開 | dで切り替え、j/kで自動的に折りたたみます |
| クリップボードにコピー | クリップボード連携 | IDはy、MarkdownはCでコピーし、トーストで通知します |
?/`/;のヘルプ | マルチレベルヘルプ | クイックリファレンス+チュートリアル+常時表示のサイドバー |
| モード切り替え対応のカンバン | カンバンのスイムレーン | ボードの状態を事前計算し、O(1)で切り替えます |
| h/lで操作する折りたたみ可能なツリー | ツリーナビゲーション | ツリーを表示項目のリストに平坦化し、j/kで移動します |
| vimで編集するためにTUIを一時停止 | エディターの起動 | ターミナルではtea.ExecProcess、GUIではバックグラウンド処理を使います |
| 展開/折りたたみ状態を記憶 | 永続化される状態 | JSONに保存し、破損時は適切にフォールバックします |
| 環境変数で調整 | 環境設定 | NO_COLOR、テーマ、デバウンス、分割比率 |
| オプション機能が利用できない場合 | グレースフルデグラデーション | 起動時に検出し、利用できない機能を非表示にします |
事前チェックリスト(すべてのTUI)
-
tea.WindowSizeMsgを処理する — すべてのコンポーネントをリサイズします -
ctrl+cを処理する — クリーンアップし、ターミナルの状態を復元します - パイプ接続されたstdin/stdoutを検出する — プレーンテキストにフォールバックします
- 最小80×24のターミナルでテストします
-
--no-tui/NO_TUIの回避手段を用意します - ライト背景とダーク背景の両方でテストします
-
NO_COLOR=1およびTERM=dumbでテストします
本番環境向けTUIについては、完全なチェックリスト(必須項目16件+仕上げ項目20件)を参照してください。
Charmを使わない方がよい場合
- 出力をパイプに渡す場合:
mytool | grep→ プレーンテキスト - CI/CDの場合: ターミナルがないため、フラグ/環境変数を使います
- 単純なプロンプトが1つだけの場合:
fmt.Scanfで十分かもしれません
回避手段:
if !term.IsTerminal(os.Stdin.Fd()) || os.Getenv("NO_TUI") != "" {
runPlainMode()
return
}
すべての参考資料
| 知りたいこと | 参照先 |
|---|---|
| コピーして使えるワンライナー | クイックリファレンス |
| TUIタスク用にClaudeへ渡すプロンプト | プロンプト |
| Gum/VHS/Mods/Freeze/Glow | シェルスクリプト |
| Bubble Teaのアーキテクチャ、デバッグ、アンチパターン | Go TUI |
| BubblesのコンポーネントAPI(list、table、viewportなど) | コンポーネントカタログ |
| テーマ設定、レイアウト、アニメーション、Huhフォーム、テスト | 高度なパターン |
| 高度なパターン:非同期処理、スナップショット、フォーカス制御、アダプティブレイアウト、スパークライン、カンバン、ツリー、キャッシュ | 本番環境向けアーキテクチャ |
| Wish SSHサーバー、Soft Serve、teatest | インフラストラクチャ |
コメント
Markdown 対応