--- 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) - [ ] Адаптируется ли вывод под ширину терминала --- **Итог:** Хороший консольный вывод — это баланс между информативностью и визуальной чистотой. Используйте цвета для выделения важного, иконки для быстрого сканирования, и структуру для понятности информации.