---
name: console-formatter
description: Форматирование текста для красивого вывода в консоль с использованием цветов ANSI, иконок Unicode и структурированного макета. Используйте когда нужно создать скрипт для вывода информации в терминал, CLI-инструмент, или улучшить читаемость логов.
---

# Console Formatter Skill

Этот skill помогает создавать красиво отформатированный вывод для консоли/терминала с правильной типографикой, цветами и структурой.

## Когда использовать

- Создание CLI-инструментов и утилит
- Форматирование логов и отчётов для терминала
- Вывод структурированной информации (статусы, прогресс, таблицы)
- Улучшение UX консольных приложений
- Создание дашбордов для терминала

## Основные принципы

### 1. Цветовая схема ANSI

Используйте ANSI escape-коды для цветов:

**Базовые цвета:**
```python
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'
ITALIC = '\033[3m'
UNDERLINE = '\033[4m'

# Цвета текста
BLACK = '\033[30m'
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'

# Яркие цвета
BRIGHT_BLACK = '\033[90m'
BRIGHT_RED = '\033[91m'
BRIGHT_GREEN = '\033[92m'
BRIGHT_YELLOW = '\033[93m'
BRIGHT_BLUE = '\033[94m'
BRIGHT_MAGENTA = '\033[95m'
BRIGHT_CYAN = '\033[96m'
BRIGHT_WHITE = '\033[97m'

# Цвета фона
BG_BLACK = '\033[40m'
BG_RED = '\033[41m'
BG_GREEN = '\033[42m'
BG_YELLOW = '\033[43m'
BG_BLUE = '\033[44m'
BG_MAGENTA = '\033[45m'
BG_CYAN = '\033[46m'
BG_WHITE = '\033[47m'
```

**Рекомендуемая палитра для информации:**
- 🟢 Успех: `GREEN` или `BRIGHT_GREEN`
- 🔴 Ошибка: `RED` или `BRIGHT_RED`
- 🟡 Предупреждение: `YELLOW` или `BRIGHT_YELLOW`
- 🔵 Информация: `CYAN` или `BRIGHT_CYAN`
- ⚪ Второстепенное: `DIM` или `BRIGHT_BLACK`

### 2. Unicode-иконки

Используйте эмодзи и специальные символы для визуального разделения:

**Статусы:**
- ✅ `✅` — успех, выполнено
- ❌ `❌` — ошибка, провал
- ⚠️  `⚠️` — предупреждение
- ℹ️  `ℹ️` — информация
- 🔄 `🔄` — в процессе, загрузка
- ⏳ `⏳` — ожидание
- 🎯 `🎯` — цель, задача
- 📋 `📋` — список, документ
- 🔍 `🔍` — поиск, проверка
- 💡 `💡` — совет, идея

**Структурные элементы:**
- 📅 `📅` — дата
- 👤 `👤` — пользователь, автор
- 📊 `📊` — статистика, метрики
- 🏷️  `🏷️` — тег, категория
- 🔗 `🔗` — ссылка
- 📁 `📁` — папка
- 📄 `📄` — файл
- ⚙️  `⚙️` — настройки, конфигурация

**Box-drawing символы:**
```
┌─┬─┐  ╔═╦═╗  ┏━┳━┓
├─┼─┤  ╠═╬═╣  ┣━╋━┫
└─┴─┘  ╚═╩═╝  ┗━┻━┛
│ ─    ║ ═    ┃ ━
```

### 3. Структурирование вывода

**Заголовки и разделители:**
```python
def print_header(text):
    width = 60
    print(f"\n{BOLD}{CYAN}{'═' * width}{RESET}")
    print(f"{BOLD}{CYAN}{text.center(width)}{RESET}")
    print(f"{BOLD}{CYAN}{'═' * width}{RESET}\n")

def print_section(text):
    print(f"\n{BOLD}{BLUE}▸ {text}{RESET}")
    print(f"{DIM}{'─' * 60}{RESET}")
```

**Списки с отступами:**
```python
def print_item(icon, title, details=None, indent=0):
    prefix = "  " * indent
    print(f"{prefix}{icon} {BOLD}{title}{RESET}")
    if details:
        print(f"{prefix}  {DIM}{details}{RESET}")
```

**Таблицы:**
```python
def print_table(headers, rows):
    # Вычисляем ширину колонок
    col_widths = [max(len(str(row[i])) for row in [headers] + rows) 
                  for i in range(len(headers))]
    
    # Верхняя граница
    print("┌" + "┬".join("─" * (w + 2) for w in col_widths) + "┐")
    
    # Заголовки
    header_row = "│" + "│".join(f" {BOLD}{h:<{w}}{RESET} " 
                                 for h, w in zip(headers, col_widths)) + "│"
    print(header_row)
    
    # Разделитель
    print("├" + "┼".join("─" * (w + 2) for w in col_widths) + "┤")
    
    # Данные
    for row in rows:
        data_row = "│" + "│".join(f" {str(cell):<{w}} " 
                                  for cell, w in zip(row, col_widths)) + "│"
        print(data_row)
    
    # Нижняя граница
    print("└" + "┴".join("─" * (w + 2) for w in col_widths) + "┘")
```

### 4. Паттерны форматирования

**Временные метки:**
```python
from datetime import datetime

def format_timestamp(dt):
    return f"{DIM}{dt.strftime('%Y-%m-%d %H:%M:%S')}{RESET}"

# Использование
print(f"📅 {format_timestamp(datetime.now())} — {BOLD}sergeinotevskii{RESET}")
```

**Статус-бары:**
```python
def progress_bar(current, total, width=40):
    percent = current / total
    filled = int(width * percent)
    bar = "█" * filled + "░" * (width - filled)
    
    color = GREEN if percent == 1.0 else YELLOW if percent > 0.5 else RED
    print(f"{color}[{bar}]{RESET} {int(percent * 100)}%")
```

**Блоки кода/цитат:**
```python
def print_quote(text, author=None):
    lines = text.split('\n')
    print(f"{DIM}┌─{RESET}")
    for line in lines:
        print(f"{DIM}│{RESET} {ITALIC}{line}{RESET}")
    print(f"{DIM}└─{RESET}")
    if author:
        print(f"  {DIM}— {author}{RESET}")
```

**Многоуровневые списки:**
```python
def print_tree_item(text, level=0, is_last=False):
    if level == 0:
        prefix = ""
        connector = ""
    else:
        prefix = "  " * (level - 1)
        connector = "└─ " if is_last else "├─ "
    
    print(f"{DIM}{prefix}{connector}{RESET}{text}")
```

### 5. Адаптивность и доступность

**Определение поддержки цветов:**
```python
import sys
import os

def supports_color():
    """Проверяет, поддерживает ли терминал цвета"""
    if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
        return False
    
    if os.environ.get('TERM') == 'dumb':
        return False
    
    return True

# Условное использование
def colorize(text, color):
    if supports_color():
        return f"{color}{text}{RESET}"
    return text
```

**NO_COLOR стандарт:**
```python
def should_use_color():
    """Уважает NO_COLOR environment variable"""
    return 'NO_COLOR' not in os.environ and supports_color()
```

### 6. Полезные хелперы

**Обрезка длинного текста:**
```python
def truncate(text, max_length=50, suffix="..."):
    if len(text) <= max_length:
        return text
    return text[:max_length - len(suffix)] + suffix
```

**Выравнивание текста:**
```python
def align_columns(*columns, spacing=2):
    """Выравнивает текст по колонкам"""
    widths = [max(len(row[i]) for row in zip(*columns)) 
              for i in range(len(columns[0]))]
    
    for row in zip(*columns):
        line = (" " * spacing).join(
            f"{cell:<{width}}" for cell, width in zip(row, widths)
        )
        print(line)
```

**Обёртка текста:**
```python
import textwrap

def wrap_text(text, width=70, indent=0):
    """Переносит длинный текст с отступом"""
    wrapper = textwrap.TextWrapper(
        width=width,
        initial_indent=" " * indent,
        subsequent_indent=" " * indent
    )
    return wrapper.fill(text)
```

## Примеры реализации

### Пример 1: Форматирование поста из вашего примера

```python
#!/usr/bin/env python3
"""Форматирование постов для консоли"""

# ANSI коды
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'
ITALIC = '\033[3m'

CYAN = '\033[36m'
YELLOW = '\033[33m'
GREEN = '\033[32m'
BRIGHT_BLACK = '\033[90m'

def format_post(date, time, author, title, content):
    """Форматирует пост в красивом виде"""
    
    # Заголовок с датой и автором
    print(f"\n{BOLD}{CYAN}{'─' * 70}{RESET}")
    print(f"{DIM}📅 {date} {time}{RESET} — {BOLD}{author}{RESET}")
    print(f"{BOLD}{CYAN}{'─' * 70}{RESET}\n")
    
    # Заголовок поста
    if title:
        print(f"  {BOLD}{YELLOW}{title}{RESET}")
        print(f"  {DIM}{'─' * 68}{RESET}\n")
    
    # Основной контент с отступом
    for paragraph in content.split('\n\n'):
        if paragraph.strip():
            wrapped = wrap_paragraph(paragraph.strip(), width=68, indent=2)
            print(wrapped)
            print()
    
    print(f"{BOLD}{CYAN}{'─' * 70}{RESET}\n")

def wrap_paragraph(text, width=70, indent=0):
    """Переносит параграф с отступом"""
    import textwrap
    wrapper = textwrap.TextWrapper(
        width=width,
        initial_indent=" " * indent,
        subsequent_indent=" " * indent
    )
    return wrapper.fill(text)

# Пример использования
if __name__ == "__main__":
    format_post(
        date="2026-01-23",
        time="22:19:33",
        author="sergeinotevskii",
        title="Все собрал? Но какой ценой? (с)",
        content="""Продолжая историю о том, как стал двигаться в сторону единого слоя памяти для используемых аи-инструментов.

С сервисом памяти я определился, теперь же возникла необходимость забрать из ChatGPT диалоги. Хотя бы за последние пол года (считаю что если в течении полугода что-то не появлялось в контексте, вероятность того что мне оно понадобиться крайне мала).

Я был уверен что у сервиса есть возможность "легально" вытащить все свои диалоги (спасибо GDPR).

Но вот незадача - похоже тариф Team (он же "бизнес") в ChatGPT в какой-то серой зоне, между персональным и ent. Прикол в том, что при попытке выгрузить свои чаты через их стандартный privacy-portal (доступно для Plus) мне приходит отбивка "Вы Ent-клиент, используйте Compliance API". Но для API нужен ключ который у меня так и не получилось достать (ох уж эти понятные IT системы, да?).

Решение

И вроде можно было бы навайбкодить скрипт, чтобы вытянуть чаты прям из браузера, но мне только что показали свежую програмку от Байрама - Retain. Задумка там конечно глубже (почитайте), но для меня в моменте было главным, что с помощью нее можно через ui интерфейс быстренько выгрузить все диалоги из ChatGPT, Codex, Claude. После чего все бережно складывается в бд на ноуте.

А еще там же интересная аналитика и быстрый поиск по этим самым диалогам.

Быстренько не совсем получилось, тк мое прошлое в QA(это я так оправдываю то что все ломаю) дало знать о себе и синхронизация на Team тарифе сходу не запустилась (понять можно, таких извращенцев, кто для личных целей использует корп тариф еще поискать надо). Я сделал PR c фиксом, который был оперативно принят, за что отдельное спасибо.

Тулза кстати также подтягивает диалоги из Claude Code, Cursor, OpenCode, Gemini и тд. Короче как говорил Танос "Все собрал".

А что дальше? Буду парсить диалоги чтобы отправить их в Hindsight)"""
    )
```

### Пример 2: CLI-утилита с прогрессом

```python
#!/usr/bin/env python3
"""Пример CLI с прогрессом и статусами"""

import time
import sys

# ANSI коды
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'

RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
CYAN = '\033[36m'

def print_status(icon, message, status=None):
    """Выводит статус операции"""
    status_colors = {
        'success': GREEN,
        'error': RED,
        'warning': YELLOW,
        'info': CYAN
    }
    
    print(f"{icon} {message}", end='')
    
    if status:
        color = status_colors.get(status, RESET)
        status_text = {
            'success': '✅ Готово',
            'error': '❌ Ошибка',
            'warning': '⚠️  Предупреждение',
            'info': 'ℹ️  Инфо'
        }.get(status, status)
        
        print(f" {color}{status_text}{RESET}")
    else:
        print()

def animate_spinner(message, duration=2):
    """Анимированный спиннер"""
    frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
    end_time = time.time() + duration
    
    i = 0
    while time.time() < end_time:
        frame = frames[i % len(frames)]
        print(f"\r{CYAN}{frame}{RESET} {message}...", end='', flush=True)
        time.sleep(0.1)
        i += 1
    
    print(f"\r✅ {message}... {GREEN}Готово{RESET}    ")

def progress_bar(current, total, width=40, label=""):
    """Прогресс-бар"""
    percent = current / total
    filled = int(width * percent)
    bar = "█" * filled + "░" * (width - filled)
    
    if percent < 0.5:
        color = RED
    elif percent < 0.9:
        color = YELLOW
    else:
        color = GREEN
    
    percentage = int(percent * 100)
    print(f"\r{label} {color}[{bar}]{RESET} {percentage}%", end='', flush=True)
    
    if current == total:
        print()  # Перевод строки в конце

# Демонстрация
if __name__ == "__main__":
    print(f"\n{BOLD}{CYAN}{'═' * 60}{RESET}")
    print(f"{BOLD}{CYAN}{'Синхронизация диалогов'.center(60)}{RESET}")
    print(f"{BOLD}{CYAN}{'═' * 60}{RESET}\n")
    
    print_status("🔍", "Поиск диалогов ChatGPT")
    animate_spinner("Подключение к API")
    
    print_status("📥", "Загрузка диалогов", "info")
    for i in range(1, 101):
        progress_bar(i, 100, label="Прогресс:")
        time.sleep(0.02)
    
    print_status("💾", "Сохранение в базу данных", "success")
    print_status("🔄", "Синхронизация завершена", "success")
    
    print(f"\n{DIM}{'─' * 60}{RESET}")
    print(f"  {BOLD}Статистика:{RESET}")
    print(f"  {DIM}├─{RESET} Обработано диалогов: {BOLD}{YELLOW}247{RESET}")
    print(f"  {DIM}├─{RESET} Сообщений: {BOLD}{CYAN}1,834{RESET}")
    print(f"  {DIM}└─{RESET} Размер БД: {BOLD}{GREEN}12.4 MB{RESET}")
    print(f"{DIM}{'─' * 60}{RESET}\n")
```

### Пример 3: Логгер с уровнями

```python
#!/usr/bin/env python3
"""Красивый логгер для консоли"""

from datetime import datetime
from enum import Enum

# ANSI коды
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'

RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
CYAN = '\033[36m'
MAGENTA = '\033[35m'
BRIGHT_BLACK = '\033[90m'

class LogLevel(Enum):
    DEBUG = (BRIGHT_BLACK, "🔍", "DEBUG")
    INFO = (CYAN, "ℹ️ ", "INFO ")
    SUCCESS = (GREEN, "✅", "OK   ")
    WARNING = (YELLOW, "⚠️ ", "WARN ")
    ERROR = (RED, "❌", "ERROR")
    CRITICAL = (MAGENTA, "🚨", "CRIT ")

class Logger:
    def __init__(self, name="App", show_timestamp=True):
        self.name = name
        self.show_timestamp = show_timestamp
    
    def _log(self, level: LogLevel, message: str, details=None):
        color, icon, level_text = level.value
        
        # Timestamp
        timestamp = ""
        if self.show_timestamp:
            now = datetime.now().strftime("%H:%M:%S")
            timestamp = f"{DIM}[{now}]{RESET} "
        
        # Основное сообщение
        print(f"{timestamp}{icon} {color}{BOLD}{level_text}{RESET} {BOLD}{message}{RESET}")
        
        # Детали с отступом
        if details:
            for line in details.split('\n'):
                if line.strip():
                    print(f"  {DIM}│{RESET} {line}")
    
    def debug(self, message, details=None):
        self._log(LogLevel.DEBUG, message, details)
    
    def info(self, message, details=None):
        self._log(LogLevel.INFO, message, details)
    
    def success(self, message, details=None):
        self._log(LogLevel.SUCCESS, message, details)
    
    def warning(self, message, details=None):
        self._log(LogLevel.WARNING, message, details)
    
    def error(self, message, details=None):
        self._log(LogLevel.ERROR, message, details)
    
    def critical(self, message, details=None):
        self._log(LogLevel.CRITICAL, message, details)

# Использование
if __name__ == "__main__":
    logger = Logger("Retain")
    
    logger.info("Запуск синхронизации")
    logger.debug("Загрузка конфигурации", "Config path: /home/user/.retain/config.json")
    logger.success("Подключение к ChatGPT API установлено")
    logger.warning("Найдены дубликаты диалогов", "3 диалога будут пропущены")
    logger.error("Не удалось загрузить диалог #1234", "API returned 403 Forbidden")
    logger.success("Синхронизация завершена", "Обработано: 247 диалогов\nВремя: 3m 42s")
```

## Рекомендации по дизайну

### Визуальная иерархия

1. **Самое важное** — жирный текст, яркие цвета, иконки
2. **Второстепенное** — обычный текст
3. **Вспомогательное** — тусклый текст (DIM), маленькие иконки

### Принцип "меньше — лучше"

- Не перегружайте цветами — 2-3 цвета на экран
- Используйте иконки там, где они действительно помогают
- Оставляйте пустое пространство для "дыхания"

### Консистентность

- Одинаковые операции — одинаковые иконки и цвета
- Единый стиль разделителей
- Постоянная структура вывода

### Производительность

- Избегайте частых перерисовок всего экрана
- Используйте `\r` для обновления одной строки
- Batch-выводите большие объёмы данных

## Библиотеки

Для более сложных случаев рассмотрите:

- **Rich** — полнофункциональная библиотека для красивого вывода
- **Colorama** — кросс-платформенная поддержка ANSI
- **Click** — для создания CLI с красивым выводом
- **Blessed** — для интерактивных TUI
- **Textual** — для полноценных терминальных приложений

## Итоговый чеклист

При создании консольного вывода проверьте:

- [ ] Используются ли цвета осмысленно (успех=зелёный, ошибка=красный)
- [ ] Есть ли визуальная иерархия (заголовки, разделители)
- [ ] Читаем ли текст без цветов (для NO_COLOR режима)
- [ ] Не перегружен ли вывод (достаточно ли пробелов)
- [ ] Консистентны ли иконки и форматирование
- [ ] Работает ли в разных терминалах (проверьте на macOS/Linux/Windows)
- [ ] Адаптируется ли вывод под ширину терминала

---

**Итог:** Хороший консольный вывод — это баланс между информативностью и визуальной чистотой. Используйте цвета для выделения важного, иконки для быстрого сканирования, и структуру для понятности информации.
