CLI OUTPUT LIBRARY - nself-org/cli GitHub Wiki

CLI Output Library

Standardized output formatting for nself CLI commands.

Overview

The cli-output.sh library provides consistent, cross-platform CLI output formatting with:

  • Bash 3.2+ compatibility - Works on macOS, Linux, WSL
  • NO_COLOR support - Respects user preferences
  • CI/TTY detection - Adapts to terminal vs. non-interactive environments
  • Comprehensive API - 40+ functions for all output needs
  • Zero dependencies - Pure Bash, no external tools required

Quick Start

# Source the library
source "src/lib/utils/cli-output.sh"

# Basic usage
cli_success "Operation completed successfully"
cli_error "Failed to connect to database"
cli_warning "Port 8080 is already in use"
cli_info "Loading configuration..."

Installation

The library is automatically available in all nself CLI commands:

# In any CLI command file
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/../lib/utils/cli-output.sh"

API Reference

Basic Messages

cli_success "message"

Print a success message with checkmark icon.

cli_success "Database connection established"
# Output: βœ“ Database connection established (in green)

cli_error "message"

Print an error message with cross icon. Outputs to stderr.

cli_error "Failed to read configuration file"
# Output: βœ— Failed to read configuration file (in red, to stderr)

cli_warning "message"

Print a warning message with warning icon. Outputs to stderr.

cli_warning "Configuration file not found, using defaults"
# Output: ⚠ Configuration file not found, using defaults (in yellow, to stderr)

cli_info "message"

Print an informational message with info icon.

cli_info "Scanning for services..."
# Output: β„Ή Scanning for services... (in blue)

cli_debug "message"

Print a debug message (only when DEBUG=true).

DEBUG=true cli_debug "Variable value: $var"
# Output: [DEBUG] Variable value: example (in magenta)

cli_message "message"

Print a plain message without icons or colors.

cli_message "Hello, world"
# Output: Hello, world

cli_bold "message"

Print a bold message.

cli_bold "Important Notice"
# Output: Important Notice (bold)

cli_dim "message"

Print a dimmed/subtle message.

cli_dim "Additional context information"
# Output: Additional context information (dimmed)

Sections and Headers

cli_section "title"

Print a section header with arrow.

cli_section "Database Configuration"
# Output:
# β†’ Database Configuration (bold, with spacing)

cli_header "title"

Print a major section header with double-line box.

cli_header "Build Process"
# Output:
# ════════════════════════════════════════════════════════════
#                         Build Process
# ════════════════════════════════════════════════════════════

cli_step current total "message"

Print a step indicator for multi-step processes.

cli_step 1 5 "Installing dependencies"
cli_step 2 5 "Running tests"
# Output:
# βš™ Step 1/5 ─ Installing dependencies
# βš™ Step 2/5 ─ Running tests

Boxes

cli_box "message" [type]

Draw a simple box around text.

Types: info (default), success, error, warning

cli_box "Build completed successfully" "success"
# Output:
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
# β”‚  Build completed successfully  β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

cli_box_detailed "title" "content"

Draw an enhanced box with title and word-wrapped content.

cli_box_detailed "Important Notice" "This is a longer message that will be wrapped properly within the box boundaries to fit the standard 60-character width."
# Output:
# ╔══════════════════════════════════════════════════════════╗
# β•‘                    Important Notice                      β•‘
# β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
# β•‘ This is a longer message that will be wrapped properly   β•‘
# β•‘ within the box boundaries to fit the standard            β•‘
# β•‘ 60-character width.                                      β•‘
# β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Tables

Tables automatically calculate column widths based on headers.

cli_table_header "Col1" "Col2" "Col3"

Print table header with column names.

cli_table_row "val1" "val2" "val3"

Print table row with values.

cli_table_footer "Col1" "Col2" "Col3"

Print table footer (closes the table).

Example:

cli_table_header "Service" "Status" "Port"
cli_table_row "postgres" "running" "5432"
cli_table_row "hasura" "running" "8080"
cli_table_row "auth" "stopped" "4000"
cli_table_footer "Service" "Status" "Port"

# Output:
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”
# β”‚ Service β”‚ Status  β”‚ Port β”‚
# β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€
# β”‚ postgresβ”‚ running β”‚ 5432 β”‚
# β”‚ hasura  β”‚ running β”‚ 8080 β”‚
# β”‚ auth    β”‚ stopped β”‚ 4000 β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜

Lists

cli_list_item "text"

Print a bullet list item.

cli_list_item "First item"
cli_list_item "Second item"
# Output:
#   β€’ First item
#   β€’ Second item

cli_list_numbered number "text"

Print a numbered list item.

cli_list_numbered 1 "First task"
cli_list_numbered 2 "Second task"
# Output:
#   1. First task
#   2. Second task

cli_list_checked "text"

Print a checked checklist item.

cli_list_checked "Completed task"
# Output:
#   [βœ“] Completed task (in green)

cli_list_unchecked "text"

Print an unchecked checklist item.

cli_list_unchecked "Pending task"
# Output:
#   [ ] Pending task (dimmed)

Progress Indicators

cli_progress "task" current total

Show a progress bar for a task.

cli_progress "Building project" 45 100
# Output:
# βš™ Building project [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘]  45%

cli_progress "Building project" 100 100
# Output:
# βš™ Building project [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% βœ“

cli_spinner_start "message"

Start an animated spinner (returns PID).

spinner_pid=$(cli_spinner_start "Loading configuration")
# Do work...
cli_spinner_stop "$spinner_pid" "Configuration loaded"

# Output (animated):
# β ‹ Loading configuration...
# (becomes)
# βœ“ Configuration loaded

Note: Spinners only work in interactive terminals. In CI/non-TTY, a simple message is printed instead.


Special Output

cli_summary "title" "item1" "item2" ...

Print a summary box with multiple items.

cli_summary "Build Complete" \
  "5 services started" \
  "Database initialized" \
  "Nginx configured"

# Output:
# ╔══════════════════════════════════════════════════════════╗
# β•‘                  β˜… Build Complete β˜…                      β•‘
# β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
# β•‘  β€’ 5 services started                                    β•‘
# β•‘  β€’ Database initialized                                  β•‘
# β•‘  β€’ Nginx configured                                      β•‘
# β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

cli_banner "title" ["subtitle"]

Print a banner for major events.

cli_banner "nself v1.0.0" "Modern Full-Stack Platform"

# Output:
# ╔══════════════════════════════════════════════════════════╗
# β•‘                                                          β•‘
# β•‘                      nself v1.0.0                        β•‘
# β•‘               Modern Full-Stack Platform                 β•‘
# β•‘                                                          β•‘
# β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

cli_separator [width]

Print a horizontal separator line.

cli_separator     # 60 characters (default)
cli_separator 40  # 40 characters

# Output:
# ────────────────────────────────────────────────────────────

Utilities

cli_strip_colors

Remove ANSI color codes from text (useful for logging).

colored_output=$(cli_success "Done")
plain_output=$(echo "$colored_output" | cli_strip_colors)
echo "$plain_output" >> logfile.txt

cli_blank [count]

Print blank line(s).

cli_blank     # 1 blank line
cli_blank 3   # 3 blank lines

cli_center "text" width

Center text within a given width.

cli_center "Centered Text" 60
# Output (centered within 60 characters):
#                      Centered Text

cli_indent "message" [level]

Print an indented message.

cli_indent "Level 1 indent" 1
cli_indent "Level 2 indent" 2
cli_indent "Level 3 indent" 3
# Output:
#   Level 1 indent
#     Level 2 indent
#       Level 3 indent

Environment Variables

NO_COLOR

The library respects the NO_COLOR environment variable (no-color.org):

# Disable all colors
export NO_COLOR=1
nself build

# Colors will be disabled for all output

DEBUG

Enable debug messages:

# Show debug messages
DEBUG=true nself build

# Debug messages will be shown

Platform Compatibility

Bash 3.2+ Support

The library is fully compatible with Bash 3.2 (default on macOS):

  • βœ… Uses printf (not echo -e)
  • βœ… No Bash 4+ features (associative arrays, lowercase expansion)
  • βœ… No external dependencies
  • βœ… Tested on macOS, Linux, WSL

Terminal Detection

The library automatically detects terminal capabilities:

# Interactive terminal
if [[ -t 1 ]]; then
  # Colors enabled, animations work
fi

# CI/non-TTY
if [[ ! -t 1 ]]; then
  # Colors may be disabled, spinners become simple messages
fi

Usage Patterns

Command Structure

Standard pattern for nself CLI commands:

#!/usr/bin/env bash
# command.sh - Description

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/../lib/utils/cli-output.sh"

main() {
  cli_header "Command Name"

  cli_section "Phase 1"
  cli_info "Starting phase 1..."
  # Do work
  cli_success "Phase 1 complete"

  cli_section "Phase 2"
  cli_info "Starting phase 2..."
  # Do work
  cli_success "Phase 2 complete"

  cli_summary "Operation Complete" \
    "Phase 1: Done" \
    "Phase 2: Done"
}

main "$@"

Error Handling

perform_operation() {
  cli_info "Starting operation..."

  if ! some_command; then
    cli_error "Operation failed: some_command returned error"
    cli_warning "Check logs for more details"
    return 1
  fi

  cli_success "Operation completed successfully"
  return 0
}

Multi-Step Processes

build_project() {
  local steps=5

  cli_header "Build Process"

  cli_step 1 $steps "Installing dependencies"
  npm install || { cli_error "npm install failed"; return 1; }

  cli_step 2 $steps "Running linter"
  npm run lint || { cli_warning "Linting found issues"; }

  cli_step 3 $steps "Running tests"
  npm test || { cli_error "Tests failed"; return 1; }

  cli_step 4 $steps "Building application"
  npm run build || { cli_error "Build failed"; return 1; }

  cli_step 5 $steps "Cleanup"
  npm run clean

  cli_summary "Build Complete" \
    "Dependencies: Installed" \
    "Tests: Passed" \
    "Build: Successful"
}

Progress Tracking

process_files() {
  local files=("file1.txt" "file2.txt" "file3.txt")
  local total=${#files[@]}
  local current=0

  for file in "${files[@]}"; do
    ((current++))
    cli_progress "Processing files" $current $total

    # Process file
    process_file "$file"
  done
}

Long-Running Operations

load_configuration() {
  local spinner_pid
  spinner_pid=$(cli_spinner_start "Loading configuration")

  # Simulate long operation
  sleep 3

  cli_spinner_stop "$spinner_pid" "Configuration loaded successfully"
}

Service Status Display

show_status() {
  cli_header "Service Status"

  cli_table_header "Service" "Status" "Port" "Health"
  cli_table_row "postgres" "running" "5432" "healthy"
  cli_table_row "hasura" "running" "8080" "healthy"
  cli_table_row "auth" "stopped" "4000" "n/a"
  cli_table_row "nginx" "running" "443" "healthy"
  cli_table_footer "Service" "Status" "Port" "Health"
}

Testing

Run the test suite:

bash src/tests/unit/test-cli-output.sh

Tests include:

  • βœ… All message types
  • βœ… Sections and headers
  • βœ… Boxes and borders
  • βœ… Lists (bullet, numbered, checkbox)
  • βœ… Tables
  • βœ… Progress bars
  • βœ… Spinners
  • βœ… Summaries and banners
  • βœ… NO_COLOR support
  • βœ… Non-TTY output
  • βœ… Bash 3.2 compatibility

Migration Guide

From display.sh

# Old
log_info "message"
log_success "message"
log_error "message"
log_warning "message"

# New
cli_info "message"
cli_success "message"
cli_error "message"
cli_warning "message"

From output-formatter-v2.sh

# Old
format_success "message"
format_error "message"
format_warning "message"
format_info "message"

# New
cli_success "message"
cli_error "message"
cli_warning "message"
cli_info "message"

From Raw printf/echo

# Old
echo -e "\033[32mβœ“\033[0m Success"
printf "\033[31mβœ—\033[0m Error\n"

# New
cli_success "Success"
cli_error "Error"

Color Reference

Available Colors

All colors are exported as constants:

CLI_RESET       # Reset all formatting
CLI_BOLD        # Bold text
CLI_DIM         # Dimmed text
CLI_UNDERLINE   # Underlined text

# Standard colors
CLI_RED         CLI_GREEN       CLI_YELLOW
CLI_BLUE        CLI_MAGENTA     CLI_CYAN
CLI_WHITE       CLI_BLACK

# Bright colors
CLI_BRIGHT_RED          CLI_BRIGHT_GREEN
CLI_BRIGHT_YELLOW       CLI_BRIGHT_BLUE
CLI_BRIGHT_MAGENTA      CLI_BRIGHT_CYAN
CLI_BRIGHT_WHITE

Custom Colored Output

# Manual color usage
printf "%b%s%b\n" "${CLI_GREEN}" "Custom green text" "${CLI_RESET}"

# Combined formatting
printf "%b%b%s%b\n" "${CLI_BOLD}" "${CLI_RED}" "Bold red text" "${CLI_RESET}"

Icon Reference

Available Icons

CLI_ICON_SUCCESS    # βœ“
CLI_ICON_ERROR      # βœ—
CLI_ICON_WARNING    # ⚠
CLI_ICON_INFO       # β„Ή
CLI_ICON_ARROW      # β†’
CLI_ICON_BULLET     # β€’
CLI_ICON_CHECK      # βœ“
CLI_ICON_CROSS      # βœ—
CLI_ICON_STAR       # β˜…
CLI_ICON_GEAR       # βš™
CLI_ICON_ROCKET     # πŸš€
CLI_ICON_PACKAGE    # πŸ“¦
CLI_ICON_FIRE       # πŸ”₯
CLI_ICON_SPARKLES   # ✨

Custom Icon Usage

printf "%b%s%b %s\n" \
  "${CLI_BLUE}" "${CLI_ICON_ROCKET}" "${CLI_RESET}" \
  "Launching application"

Best Practices

1. Consistent Hierarchy

cli_header "Top-level operation"      # Major sections
cli_section "Sub-operation"           # Sub-sections
cli_info "Detailed step"              # Individual actions

2. Meaningful Icons

  • Use cli_success for completed operations
  • Use cli_error for failures that stop execution
  • Use cli_warning for issues that don't stop execution
  • Use cli_info for informational messages

3. Progress Feedback

Always provide feedback for long operations:

# Bad
perform_long_operation  # User sees nothing

# Good
cli_info "Starting long operation..."
perform_long_operation
cli_success "Operation completed"

# Better
spinner_pid=$(cli_spinner_start "Performing operation")
perform_long_operation
cli_spinner_stop "$spinner_pid" "Operation completed"

4. Error Context

Provide actionable information with errors:

# Bad
cli_error "Failed"

# Good
cli_error "Failed to connect to database"
cli_info "Check that PostgreSQL is running: nself status"

5. Summaries

End complex operations with summaries:

cli_summary "Build Complete" \
  "Duration: 2m 34s" \
  "Warnings: 0" \
  "Errors: 0" \
  "Services started: 5"

Troubleshooting

Colors Not Showing

  1. Check NO_COLOR environment variable: echo $NO_COLOR
  2. Verify terminal supports colors: echo -e "\033[31mRed\033[0m"
  3. Test with: cli_success "Test" (should be green)

Spinners Not Animating

Spinners only work in interactive terminals. In CI or when piped, they become simple messages (by design).

Box Characters Not Displaying

Ensure terminal supports UTF-8:

echo $LANG  # Should contain UTF-8

Wide Output Wrapping

The library uses 60-character width by default. For narrower terminals:

# Most functions respect this width
cli_box "Text"           # 60 chars
cli_separator 40         # 40 chars (custom)

Performance

The library is optimized for performance:

  • No external commands - Pure Bash, no sed/awk/etc
  • Minimal string operations - Direct printf usage
  • Lazy evaluation - Colors only loaded when needed
  • TTY detection - Skips animations in non-interactive mode

Typical overhead: <1ms per function call


Contributing

When adding new functions:

  1. Use printf (never echo -e)
  2. Support NO_COLOR
  3. Test in Bash 3.2
  4. Add to test suite
  5. Document in this file
  6. Export the function

License

Part of nself - MIT License


See Also

⚠️ **GitHub.com Fallback** ⚠️