|
|
@@ -1,25 +1,46 @@
|
|
|
#!/usr/bin/env bash
|
|
|
-# term.sh — shared terminal-output helpers for claude-mods skills.
|
|
|
+# term.sh — terminal panel design system for claude-mods skills.
|
|
|
#
|
|
|
# Source from any skill script:
|
|
|
# LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../_lib" && pwd)"
|
|
|
# . "$LIB/term.sh"
|
|
|
# term_init
|
|
|
#
|
|
|
-# Honors: NO_COLOR, FORCE_COLOR, TERM_ASCII=1.
|
|
|
+# Honors: NO_COLOR, FORCE_COLOR, TERM_ASCII=1, FLEET_ASCII=1 (legacy).
|
|
|
# Status: experimental — see docs/DESIGN.md.
|
|
|
|
|
|
# Guard against double-sourcing.
|
|
|
[[ -n "${__TERM_SH_LOADED:-}" ]] && return 0
|
|
|
__TERM_SH_LOADED=1
|
|
|
|
|
|
-# Globals populated by term_init.
|
|
|
+# ─── Globals (populated by term_init) ──────────────────────────────────────
|
|
|
TERM_TTY=0
|
|
|
TERM_COLOR=0
|
|
|
TERM_ASCII_MODE=0
|
|
|
TERM_WIDTH=80
|
|
|
|
|
|
-# State icons (set by term_init based on TERM_ASCII_MODE).
|
|
|
+# ─── ANSI escapes (empty when color disabled) ─────────────────────────────
|
|
|
+TERM_C_GREEN=""
|
|
|
+TERM_C_YELLOW=""
|
|
|
+TERM_C_ORANGE=""
|
|
|
+TERM_C_RED=""
|
|
|
+TERM_C_CYAN=""
|
|
|
+TERM_C_MAGENTA=""
|
|
|
+TERM_C_DIM=""
|
|
|
+TERM_C_OFF=""
|
|
|
+
|
|
|
+# ─── Tree connectors (set by term_init based on TERM_ASCII_MODE) ──────────
|
|
|
+TERM_TREE_BRANCH="" # ├─ / +-
|
|
|
+TERM_TREE_LAST="" # └─ / `-
|
|
|
+TERM_TREE_VERT="" # │ / |
|
|
|
+
|
|
|
+# ─── Panel chrome ─────────────────────────────────────────────────────────
|
|
|
+TERM_PANEL_TL="" # ╭ / +
|
|
|
+TERM_PANEL_BL="" # ╰ / +
|
|
|
+TERM_PANEL_HRULE="" # ─ / -
|
|
|
+TERM_PANEL_TERM="" # ● / *
|
|
|
+
|
|
|
+# ─── Legacy state icons (kept for backwards-compat with fleet.sh) ─────────
|
|
|
TERM_ICON_PENDING=""
|
|
|
TERM_ICON_READY=""
|
|
|
TERM_ICON_DONE=""
|
|
|
@@ -27,14 +48,57 @@ TERM_ICON_FAILED=""
|
|
|
TERM_ICON_WARN=""
|
|
|
TERM_ICON_HINT=""
|
|
|
|
|
|
-# ANSI escapes (empty when color disabled).
|
|
|
-TERM_C_GREEN=""
|
|
|
-TERM_C_YELLOW=""
|
|
|
-TERM_C_RED=""
|
|
|
-TERM_C_CYAN=""
|
|
|
-TERM_C_DIM=""
|
|
|
-TERM_C_OFF=""
|
|
|
+# ─── Registries (Unicode|ASCII) ───────────────────────────────────────────
|
|
|
+declare -A TERM_BRAND=(
|
|
|
+ [fleet]="⚡|[F]"
|
|
|
+ [forge]="🔨|[B]"
|
|
|
+ [psql]="🐘|[P]"
|
|
|
+ [watch]="📡|[M]"
|
|
|
+ [deploy]="🚀|[D]"
|
|
|
+ [git]="🌿|[G]"
|
|
|
+)
|
|
|
+
|
|
|
+declare -A TERM_HEALTH_GLYPH=(
|
|
|
+ [healthy]="•|(+)"
|
|
|
+ [pending]="•|(.)"
|
|
|
+ [warning]="•|(!)"
|
|
|
+ [critical]="•|(!!)"
|
|
|
+ [busted]="⬤|(X)"
|
|
|
+ [unknown]="•|(?)"
|
|
|
+)
|
|
|
+
|
|
|
+declare -A TERM_DIAGRAM_ICON=(
|
|
|
+ [user]="👤|(U)"
|
|
|
+ [web]="🌐|(W)"
|
|
|
+ [mobile]="📱|(M)"
|
|
|
+ [auth]="🔐|(A)"
|
|
|
+ [database]="🗄|(D)"
|
|
|
+ [cache]="⚡|(C)"
|
|
|
+ [queue]="📨|(Q)"
|
|
|
+ [storage]="📦|(P)"
|
|
|
+ [service]="⚙|*"
|
|
|
+ [api]="🔌|(I)"
|
|
|
+ [search]="🔍|(S)"
|
|
|
+ [timer]="⏱|(T)"
|
|
|
+ [build]="🔨|(B)"
|
|
|
+ [hook]="🪝|(H)"
|
|
|
+ [log]="📄|(F)"
|
|
|
+)
|
|
|
+
|
|
|
+# Header indicator glyph (branch/⎇)
|
|
|
+TERM_GLYPH_BRANCH=""
|
|
|
+
|
|
|
+# Inline alert glyph (▲)
|
|
|
+TERM_GLYPH_ALERT=""
|
|
|
+
|
|
|
+# Empty-state tip glyph (💡)
|
|
|
+TERM_GLYPH_TIP=""
|
|
|
+
|
|
|
+# Spinner frame banks (set by term_init; arrays keep order).
|
|
|
+TERM_SPIN_WORKING=()
|
|
|
+TERM_SPIN_HEARTBEAT=()
|
|
|
|
|
|
+# ─── term_init ────────────────────────────────────────────────────────────
|
|
|
term_init() {
|
|
|
# TTY detection — stdout only.
|
|
|
if [[ -t 1 ]]; then TERM_TTY=1; else TERM_TTY=0; fi
|
|
|
@@ -73,6 +137,15 @@ term_init() {
|
|
|
TERM_TREE_BRANCH="+-"
|
|
|
TERM_TREE_LAST="\`-"
|
|
|
TERM_TREE_VERT="|"
|
|
|
+ TERM_PANEL_TL="+"
|
|
|
+ TERM_PANEL_BL="+"
|
|
|
+ TERM_PANEL_HRULE="-"
|
|
|
+ TERM_PANEL_TERM="*"
|
|
|
+ TERM_GLYPH_BRANCH="(b)"
|
|
|
+ TERM_GLYPH_ALERT="!"
|
|
|
+ TERM_GLYPH_TIP="(i)"
|
|
|
+ TERM_SPIN_WORKING=('|' '/' '-' '\')
|
|
|
+ TERM_SPIN_HEARTBEAT=('.' ':' '*' ':')
|
|
|
else
|
|
|
TERM_ICON_PENDING="⏳"
|
|
|
TERM_ICON_READY="✅"
|
|
|
@@ -83,36 +156,73 @@ term_init() {
|
|
|
TERM_TREE_BRANCH="├─"
|
|
|
TERM_TREE_LAST="└─"
|
|
|
TERM_TREE_VERT="│"
|
|
|
+ TERM_PANEL_TL="╭"
|
|
|
+ TERM_PANEL_BL="╰"
|
|
|
+ TERM_PANEL_HRULE="─"
|
|
|
+ TERM_PANEL_TERM="●"
|
|
|
+ TERM_GLYPH_BRANCH="⎇"
|
|
|
+ TERM_GLYPH_ALERT="▲"
|
|
|
+ TERM_GLYPH_TIP="💡"
|
|
|
+ TERM_SPIN_WORKING=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
|
+ TERM_SPIN_HEARTBEAT=('·' '∙' '•' '●' '•' '∙')
|
|
|
fi
|
|
|
|
|
|
if [[ "$TERM_COLOR" -eq 1 ]]; then
|
|
|
TERM_C_GREEN=$'\033[32m'
|
|
|
TERM_C_YELLOW=$'\033[33m'
|
|
|
+ TERM_C_ORANGE=$'\033[38;5;208m'
|
|
|
TERM_C_RED=$'\033[31m'
|
|
|
TERM_C_CYAN=$'\033[36m'
|
|
|
+ TERM_C_MAGENTA=$'\033[35m'
|
|
|
TERM_C_DIM=$'\033[2m'
|
|
|
TERM_C_OFF=$'\033[0m'
|
|
|
else
|
|
|
- TERM_C_GREEN=""; TERM_C_YELLOW=""; TERM_C_RED=""
|
|
|
- TERM_C_CYAN=""; TERM_C_DIM=""; TERM_C_OFF=""
|
|
|
+ TERM_C_GREEN=""; TERM_C_YELLOW=""; TERM_C_ORANGE=""
|
|
|
+ TERM_C_RED=""; TERM_C_CYAN=""; TERM_C_MAGENTA=""
|
|
|
+ TERM_C_DIM=""; TERM_C_OFF=""
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
-# term_color <name> <text...> — wrap text in named color (green/yellow/red/cyan/dim).
|
|
|
+# ─── Color helper ─────────────────────────────────────────────────────────
|
|
|
+# term_color <name> <text...>
|
|
|
term_color() {
|
|
|
local name=$1; shift
|
|
|
local code=""
|
|
|
case "$name" in
|
|
|
- green) code="$TERM_C_GREEN" ;;
|
|
|
- yellow) code="$TERM_C_YELLOW" ;;
|
|
|
- red) code="$TERM_C_RED" ;;
|
|
|
- cyan) code="$TERM_C_CYAN" ;;
|
|
|
- dim) code="$TERM_C_DIM" ;;
|
|
|
+ green) code="$TERM_C_GREEN" ;;
|
|
|
+ yellow) code="$TERM_C_YELLOW" ;;
|
|
|
+ orange) code="$TERM_C_ORANGE" ;;
|
|
|
+ red) code="$TERM_C_RED" ;;
|
|
|
+ cyan) code="$TERM_C_CYAN" ;;
|
|
|
+ magenta) code="$TERM_C_MAGENTA" ;;
|
|
|
+ dim) code="$TERM_C_DIM" ;;
|
|
|
esac
|
|
|
printf '%s%s%s' "$code" "$*" "$TERM_C_OFF"
|
|
|
}
|
|
|
|
|
|
-# term_state_icon <STATE> — echo glyph for a known state.
|
|
|
+# ─── Registry lookup ──────────────────────────────────────────────────────
|
|
|
+# term_emoji <registry_name> <key> — returns Unicode glyph or ASCII fallback.
|
|
|
+# Internal helper; pass "BRAND", "HEALTH_GLYPH", "DIAGRAM_ICON".
|
|
|
+__term_lookup() {
|
|
|
+ local map=$1 key=$2 entry uni ascii
|
|
|
+ case "$map" in
|
|
|
+ BRAND) entry="${TERM_BRAND[$key]:-}" ;;
|
|
|
+ HEALTH_GLYPH) entry="${TERM_HEALTH_GLYPH[$key]:-}" ;;
|
|
|
+ DIAGRAM_ICON) entry="${TERM_DIAGRAM_ICON[$key]:-}" ;;
|
|
|
+ *) entry="" ;;
|
|
|
+ esac
|
|
|
+ [[ -z "$entry" ]] && { printf '%s' "?"; return; }
|
|
|
+ uni="${entry%|*}"
|
|
|
+ ascii="${entry#*|}"
|
|
|
+ if [[ "$TERM_ASCII_MODE" -eq 1 ]]; then printf '%s' "$ascii"
|
|
|
+ else printf '%s' "$uni"; fi
|
|
|
+}
|
|
|
+
|
|
|
+term_brand_glyph() { __term_lookup BRAND "$1"; }
|
|
|
+term_health_glyph() { __term_lookup HEALTH_GLYPH "$1"; }
|
|
|
+term_diagram_icon() { __term_lookup DIAGRAM_ICON "$1"; }
|
|
|
+
|
|
|
+# ─── Legacy state-icon helper (used by fleet.sh) ──────────────────────────
|
|
|
term_state_icon() {
|
|
|
case "$1" in
|
|
|
RUNNING|PENDING) printf '%s' "$TERM_ICON_PENDING" ;;
|
|
|
@@ -125,6 +235,8 @@ term_state_icon() {
|
|
|
esac
|
|
|
}
|
|
|
|
|
|
+# ─── Primitives ───────────────────────────────────────────────────────────
|
|
|
+
|
|
|
# term_repeat <char> <n>
|
|
|
term_repeat() {
|
|
|
local ch=$1 n=$2 i out=""
|
|
|
@@ -132,7 +244,274 @@ term_repeat() {
|
|
|
printf '%s' "$out"
|
|
|
}
|
|
|
|
|
|
-# term_header <title> [meta] — "── title ────── meta"
|
|
|
+# term_truncate <text> <max_cols> — ellipsis-truncate, append "…" or "..".
|
|
|
+term_truncate() {
|
|
|
+ local text=$1 max=$2
|
|
|
+ local len=${#text}
|
|
|
+ if [[ $len -le $max ]]; then printf '%s' "$text"; return; fi
|
|
|
+ local ell="…"
|
|
|
+ [[ "$TERM_ASCII_MODE" -eq 1 ]] && ell=".."
|
|
|
+ local elllen=${#ell}
|
|
|
+ printf '%s%s' "${text:0:$((max - elllen))}" "$ell"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Panel ────────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+# term_panel_open <emoji_key> <name> [right_indicator]
|
|
|
+# ╭── ⚡ name ───────── <indicator> ───●
|
|
|
+term_panel_open() {
|
|
|
+ local key=$1 name=$2 indicator=${3:-}
|
|
|
+ local emoji
|
|
|
+ emoji=$(term_brand_glyph "$key")
|
|
|
+ local left="${TERM_PANEL_TL}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE} ${emoji} $(term_color cyan "$name") "
|
|
|
+ local right=""
|
|
|
+ if [[ -n "$indicator" ]]; then
|
|
|
+ right=" $(term_color dim "$indicator") ${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}$(term_color cyan "$TERM_PANEL_TERM")"
|
|
|
+ else
|
|
|
+ right="${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}$(term_color cyan "$TERM_PANEL_TERM")"
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Visible (color-stripped) widths to size the rule fill correctly.
|
|
|
+ local left_vis="${TERM_PANEL_TL}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE} ${emoji} ${name} "
|
|
|
+ local right_vis=""
|
|
|
+ [[ -n "$indicator" ]] && right_vis=" ${indicator} ${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_TERM}" \
|
|
|
+ || right_vis="${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_TERM}"
|
|
|
+
|
|
|
+ local fill=$(( TERM_WIDTH - ${#left_vis} - ${#right_vis} ))
|
|
|
+ [[ $fill -lt 4 ]] && fill=4
|
|
|
+ local rule
|
|
|
+ rule=$(term_repeat "$TERM_PANEL_HRULE" "$fill")
|
|
|
+ printf '%s%s%s\n' "$left" "$(term_color cyan "$rule")" "$right"
|
|
|
+}
|
|
|
+
|
|
|
+# term_panel_close [hotkeys] [health_indicators]
|
|
|
+# ╰── R refresh · L land · ? help ───── • daemon • 17m ───●
|
|
|
+# `hotkeys`: pre-formatted "R refresh · L land · ? help" string.
|
|
|
+# `healths`: pre-formatted "• daemon • 17m" string.
|
|
|
+term_panel_close() {
|
|
|
+ local hotkeys=${1:-} healths=${2:-}
|
|
|
+ local left="${TERM_PANEL_BL}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE} ${hotkeys} "
|
|
|
+ local right=""
|
|
|
+ if [[ -n "$healths" ]]; then
|
|
|
+ right=" ${healths} ${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}$(term_color cyan "$TERM_PANEL_TERM")"
|
|
|
+ else
|
|
|
+ right="${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}$(term_color cyan "$TERM_PANEL_TERM")"
|
|
|
+ fi
|
|
|
+
|
|
|
+ local left_vis="${TERM_PANEL_BL}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE} ${hotkeys} "
|
|
|
+ local right_vis=""
|
|
|
+ [[ -n "$healths" ]] && right_vis=" ${healths} ${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_TERM}" \
|
|
|
+ || right_vis="${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_HRULE}${TERM_PANEL_TERM}"
|
|
|
+
|
|
|
+ local fill=$(( TERM_WIDTH - ${#left_vis} - ${#right_vis} ))
|
|
|
+ [[ $fill -lt 4 ]] && fill=4
|
|
|
+ local rule
|
|
|
+ rule=$(term_repeat "$TERM_PANEL_HRULE" "$fill")
|
|
|
+ printf '%s%s%s\n' "$left" "$(term_color cyan "$rule")" "$right"
|
|
|
+}
|
|
|
+
|
|
|
+# term_panel_vert — emit a single body-line spacer "│"
|
|
|
+term_panel_vert() {
|
|
|
+ printf '%s\n' "$(term_color dim "$TERM_TREE_VERT")"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Body components ──────────────────────────────────────────────────────
|
|
|
+
|
|
|
+# term_section <state> <label> <count>
|
|
|
+# ├── LABEL (n) (label colored by state)
|
|
|
+term_section() {
|
|
|
+ local state=$1 label=$2 count=$3
|
|
|
+ local color=""
|
|
|
+ case "$state" in
|
|
|
+ RUNNING|PENDING|CONFLICT|WARN|warning) color="yellow" ;;
|
|
|
+ READY|LANDED|DONE|OK|healthy) color="green" ;;
|
|
|
+ FAILED|ERROR|critical|alarm) color="red" ;;
|
|
|
+ *) color="" ;;
|
|
|
+ esac
|
|
|
+ local rendered_label="$label"
|
|
|
+ [[ -n "$color" ]] && rendered_label=$(term_color "$color" "$label")
|
|
|
+ printf '%s%s %s %s\n' \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color dim "$TERM_TREE_BRANCH$TERM_PANEL_HRULE")" \
|
|
|
+ "$rendered_label" \
|
|
|
+ "$(term_color dim "($count)")"
|
|
|
+}
|
|
|
+
|
|
|
+# term_summary_line <text> — dim metadata branch
|
|
|
+# ├── text
|
|
|
+term_summary_line() {
|
|
|
+ printf '%s%s %s\n' \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color dim "$TERM_TREE_BRANCH$TERM_PANEL_HRULE")" \
|
|
|
+ "$(term_color dim "$*")"
|
|
|
+}
|
|
|
+
|
|
|
+# term_leaf_line <connector> <name> <leaf_glyph> <meta> <age>
|
|
|
+# │ ├── name ●─●─●─◉ M4 ?1 12m
|
|
|
+# `connector` = ├── or └──
|
|
|
+term_leaf_line() {
|
|
|
+ local conn=$1 name=$2 leaf=$3 meta=${4:-} age=${5:-}
|
|
|
+ local trunc_name
|
|
|
+ trunc_name=$(term_truncate "$name" 28)
|
|
|
+ printf '%s %s %-28s %-14s %-10s %s\n' \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color dim "$conn$TERM_PANEL_HRULE")" \
|
|
|
+ "$trunc_name" \
|
|
|
+ "$leaf" \
|
|
|
+ "$(term_color dim "$meta")" \
|
|
|
+ "$(term_color dim "$age")"
|
|
|
+}
|
|
|
+
|
|
|
+# term_toast <emoji_key> <text> — ├── ⚡ text (dim cyan)
|
|
|
+term_toast() {
|
|
|
+ local key=$1; shift
|
|
|
+ local emoji
|
|
|
+ emoji=$(term_brand_glyph "$key")
|
|
|
+ printf '%s%s %s\n' \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color dim "$TERM_TREE_BRANCH$TERM_PANEL_HRULE")" \
|
|
|
+ "$(term_color cyan "$emoji $*")"
|
|
|
+}
|
|
|
+
|
|
|
+# term_alert <severity> <text> — ▲ message (orange/red), as a sub-row
|
|
|
+# `severity` = warning | critical
|
|
|
+term_alert() {
|
|
|
+ local sev=$1; shift
|
|
|
+ local color="orange"
|
|
|
+ [[ "$sev" == "critical" ]] && color="red"
|
|
|
+ printf '%s %s %s %s\n' \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color dim "$TERM_TREE_VERT")" \
|
|
|
+ "$(term_color "$color" "$TERM_GLYPH_ALERT")" \
|
|
|
+ "$*"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Leaf glyph builders ──────────────────────────────────────────────────
|
|
|
+
|
|
|
+# term_rail <commits_ahead> <head_state>
|
|
|
+# head_state: HEAD | CONFLICT | EMPTY
|
|
|
+# Examples:
|
|
|
+# term_rail 3 HEAD → ●─●─●─◉
|
|
|
+# term_rail 4 HEAD → ●─●─●─●─◉
|
|
|
+# term_rail 1 HEAD → ●─◉
|
|
|
+# term_rail 3 CONFLICT → ●─●─⊗
|
|
|
+# term_rail 0 EMPTY → ─
|
|
|
+term_rail() {
|
|
|
+ local n=$1 head=${2:-HEAD}
|
|
|
+ local commit="●"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && commit="*"
|
|
|
+ local link="─"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && link="-"
|
|
|
+ local headg="◉"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && headg="@"
|
|
|
+ local conflict="⊗"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && conflict="X"
|
|
|
+
|
|
|
+ if [[ $n -le 0 && "$head" == "EMPTY" ]]; then printf '%s' "$link"; return; fi
|
|
|
+
|
|
|
+ local out=""
|
|
|
+ local i
|
|
|
+ # n landed commits, joined by links
|
|
|
+ for (( i=0; i<n-1; i++ )); do
|
|
|
+ out="${out}$(term_color green "$commit")${link}"
|
|
|
+ done
|
|
|
+
|
|
|
+ # final glyph
|
|
|
+ case "$head" in
|
|
|
+ HEAD)
|
|
|
+ if [[ $n -ge 1 ]]; then out="${out}$(term_color green "$commit")${link}"; fi
|
|
|
+ out="${out}$(term_color yellow "$headg")"
|
|
|
+ ;;
|
|
|
+ CONFLICT)
|
|
|
+ if [[ $n -ge 1 ]]; then out="${out}$(term_color green "$commit")${link}"; fi
|
|
|
+ out="${out}$(term_color red "$conflict")"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ [[ $n -ge 1 ]] && out="${out}$(term_color green "$commit")"
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+ printf '%s' "$out"
|
|
|
+}
|
|
|
+
|
|
|
+# term_pip_bar <metric_type> <filled> <total>
|
|
|
+# metric_type: progress | score | capacity
|
|
|
+# filled / total are integers (e.g., 30, 100)
|
|
|
+term_pip_bar() {
|
|
|
+ local kind=$1 filled=$2 total=$3
|
|
|
+ local pip_full="▰"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && pip_full="#"
|
|
|
+ local pip_empty="▱"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && pip_empty="-"
|
|
|
+ local width=10
|
|
|
+ [[ "$total" -ne 100 && "$total" -gt 0 && "$total" -le 12 ]] && width=$total
|
|
|
+
|
|
|
+ # Pip count
|
|
|
+ local pips
|
|
|
+ if [[ "$total" -eq 100 ]]; then
|
|
|
+ pips=$(( filled / 10 ))
|
|
|
+ else
|
|
|
+ pips=$filled
|
|
|
+ fi
|
|
|
+ [[ $pips -lt 0 ]] && pips=0
|
|
|
+ [[ $pips -gt $width ]] && pips=$width
|
|
|
+
|
|
|
+ # Color selection
|
|
|
+ local color="green"
|
|
|
+ local pct=$(( total > 0 ? filled * 100 / total : 0 ))
|
|
|
+ case "$kind" in
|
|
|
+ progress) color="yellow"; [[ $pct -ge 100 ]] && color="green" ;;
|
|
|
+ score) if [[ $pct -lt 33 ]]; then color="red"
|
|
|
+ elif [[ $pct -lt 66 ]]; then color="yellow"
|
|
|
+ else color="green"; fi ;;
|
|
|
+ capacity) if [[ $pct -ge 80 ]]; then color="red"
|
|
|
+ elif [[ $pct -ge 60 ]]; then color="yellow"
|
|
|
+ else color="green"; fi ;;
|
|
|
+ esac
|
|
|
+
|
|
|
+ local i out=""
|
|
|
+ for (( i=0; i<pips; i++ )); do out="${out}$(term_color "$color" "$pip_full")"; done
|
|
|
+ for (( i=pips; i<width; i++ )); do out="${out}$(term_color dim "$pip_empty")"; done
|
|
|
+ printf '%s' "$out"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Right-side furniture ─────────────────────────────────────────────────
|
|
|
+
|
|
|
+# term_health <state> <text> — • text (colored bullet, with ⬤ for busted)
|
|
|
+# state: healthy|pending|warning|critical|busted|unknown
|
|
|
+term_health() {
|
|
|
+ local state=$1; shift
|
|
|
+ local glyph
|
|
|
+ glyph=$(term_health_glyph "$state")
|
|
|
+ local color=""
|
|
|
+ case "$state" in
|
|
|
+ healthy) color="green" ;;
|
|
|
+ pending) color="yellow" ;;
|
|
|
+ warning) color="orange" ;;
|
|
|
+ critical) color="red" ;;
|
|
|
+ busted) color="dim" ;;
|
|
|
+ *) color="dim" ;;
|
|
|
+ esac
|
|
|
+ printf '%s %s' "$(term_color "$color" "$glyph")" "$*"
|
|
|
+}
|
|
|
+
|
|
|
+# term_hotkey <key> <verb> — "R refresh" (key in cyan)
|
|
|
+term_hotkey() {
|
|
|
+ printf '%s %s' "$(term_color cyan "$1")" "$2"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Spinners (live mode) ─────────────────────────────────────────────────
|
|
|
+
|
|
|
+# term_spinner_frame <family> <tick> — return frame at `tick % frames`.
|
|
|
+# family: working | heartbeat
|
|
|
+term_spinner_frame() {
|
|
|
+ local fam=$1 tick=$2
|
|
|
+ local -a frames
|
|
|
+ case "$fam" in
|
|
|
+ working) frames=("${TERM_SPIN_WORKING[@]}") ;;
|
|
|
+ heartbeat) frames=("${TERM_SPIN_HEARTBEAT[@]}") ;;
|
|
|
+ *) printf '?'; return ;;
|
|
|
+ esac
|
|
|
+ local n=${#frames[@]}
|
|
|
+ printf '%s' "${frames[$(( tick % n ))]}"
|
|
|
+}
|
|
|
+
|
|
|
+# ─── Legacy / kept-for-compat helpers (used by older scripts) ─────────────
|
|
|
+
|
|
|
+# term_header <title> [meta] — "── title ────── meta" (legacy)
|
|
|
term_header() {
|
|
|
local title=$1 meta=${2:-}
|
|
|
local glyph="─"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && glyph="-"
|
|
|
@@ -147,14 +526,12 @@ term_header() {
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
-# term_divider [width] — plain horizontal rule.
|
|
|
term_divider() {
|
|
|
local w=${1:-$TERM_WIDTH}
|
|
|
local glyph="─"; [[ "$TERM_ASCII_MODE" -eq 1 ]] && glyph="-"
|
|
|
printf '%s\n' "$(term_repeat "$glyph" "$w")"
|
|
|
}
|
|
|
|
|
|
-# term_tree_item <icon> <label> [meta] — " <icon> label meta"
|
|
|
term_tree_item() {
|
|
|
local icon=$1 label=$2 meta=${3:-}
|
|
|
if [[ -n "$meta" ]]; then
|
|
|
@@ -164,40 +541,16 @@ term_tree_item() {
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
-# Tree connectors — set by term_init via TERM_ASCII_MODE.
|
|
|
-TERM_TREE_BRANCH="" # ├─ / +-
|
|
|
-TERM_TREE_LAST="" # └─ / `-
|
|
|
-TERM_TREE_VERT="" # │ / |
|
|
|
-
|
|
|
-# Tree-control philosophy: the connectors (├─ │ └─) are the scaffold.
|
|
|
-# Icons and labels sit AFTER the connector, never between it and the
|
|
|
-# vertical line of its parent. To render a tree:
|
|
|
-#
|
|
|
-# term_tree_node "" "$(term_tree_connector $i $last)" "⏳ RUNNING (3)"
|
|
|
-# term_tree_node "│ " "$(term_tree_connector $j $last)" "feat/auth" "12m"
|
|
|
-#
|
|
|
-# `prefix` is what comes before this row's connector — built by walking
|
|
|
-# the ancestor chain and appending TERM_TREE_VERT+" " for non-last
|
|
|
-# ancestors, or three spaces for last ancestors.
|
|
|
-
|
|
|
-# term_tree_connector <idx> <last_idx> — echo branch or last glyph.
|
|
|
term_tree_connector() {
|
|
|
if [[ "$1" -eq "$2" ]]; then printf '%s' "$TERM_TREE_LAST"
|
|
|
else printf '%s' "$TERM_TREE_BRANCH"; fi
|
|
|
}
|
|
|
|
|
|
-# term_tree_indent <is_last> — echo the 3-col continuation segment for
|
|
|
-# this ancestor: "│ " when more siblings follow, " " when last.
|
|
|
term_tree_indent() {
|
|
|
if [[ "$1" -eq 1 ]]; then printf ' '
|
|
|
else printf '%s ' "$TERM_TREE_VERT"; fi
|
|
|
}
|
|
|
|
|
|
-# term_tree_node <prefix> <connector> <label> [meta]
|
|
|
-# prefix: ancestor-chain string (built from term_tree_indent calls)
|
|
|
-# connector: result of term_tree_connector for THIS row
|
|
|
-# label: visible text (may include leading icon — won't break the line)
|
|
|
-# meta: optional dim trailing text
|
|
|
term_tree_node() {
|
|
|
local prefix=$1 conn=$2 label=$3 meta=${4:-}
|
|
|
if [[ -n "$meta" ]]; then
|
|
|
@@ -207,12 +560,10 @@ term_tree_node() {
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
-# term_table_row <c1> <c2> <c3> — fixed-width 3-col row.
|
|
|
term_table_row() {
|
|
|
printf ' %-2s %-32s %-10s %s\n' "${1:-}" "${2:-}" "${3:-}" "${4:-}"
|
|
|
}
|
|
|
|
|
|
-# term_empty <message> — dim italic-ish empty state.
|
|
|
term_empty() {
|
|
|
printf ' %s\n' "$(term_color dim "($*)")"
|
|
|
}
|