a digital person for bluesky

Improve log readability with Unicode symbols and indentation

- Replace underscore separators with Unicode box-drawing characters
- Add symbols for different log levels (✓ INFO, ⚠ WARNING, ✗ ERROR, etc.)
- Add contextual symbols for different panel types (▶ mentions, ◆ reasoning, ⚙ tools, ✎ posts)
- Indent panel content for better visual hierarchy
- Maintain --simple-logs compatibility for plain text format
- Use vertical bar separators in log timestamps for cleaner alignment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+100 -38
+100 -38
bsky.py
··· 49 49 last_archival_query = "archival memory search" 50 50 51 51 def log_with_panel(message, title=None, border_color="white"): 52 - """Log a message with simple text format""" 52 + """Log a message with Unicode box-drawing characters""" 53 53 if title: 54 - print(f"\n{title}") 55 - print("_" * len(title)) 56 - print(message) 54 + # Map old color names to appropriate symbols 55 + symbol_map = { 56 + "blue": "⚙", # Tool calls 57 + "green": "✓", # Success/completion 58 + "yellow": "◆", # Reasoning 59 + "red": "✗", # Errors 60 + "white": "▶", # Default/mentions 61 + "cyan": "✎", # Posts 62 + } 63 + symbol = symbol_map.get(border_color, "▶") 64 + 65 + print(f"\n{symbol} {title}") 66 + print(f" {'─' * len(title)}") 67 + # Indent message lines 68 + for line in message.split('\n'): 69 + print(f" {line}") 57 70 else: 58 71 print(message) 59 72 ··· 335 348 # Continue without user blocks rather than failing completely 336 349 337 350 # Get response from Letta agent 338 - # Simple text format with underscores 351 + # Format with Unicode characters 339 352 title = f"MENTION FROM @{author_handle}" 340 - print(f"\n{title}") 341 - print("_" * len(title)) 342 - print(mention_text) 353 + print(f"\n▶ {title}") 354 + print(f" {'═' * len(title)}") 355 + # Indent the mention text 356 + for line in mention_text.split('\n'): 357 + print(f" {line}") 343 358 344 359 # Log prompt details to separate logger 345 360 prompt_logger.debug(f"Full prompt being sent:\n{prompt}") ··· 365 380 if chunk.message_type == 'reasoning_message': 366 381 # Show full reasoning without truncation 367 382 if SHOW_REASONING: 368 - # Simple text format with underscores 369 - print("\nReasoning") 370 - print("_________") 371 - print(chunk.reasoning) 383 + # Format with Unicode characters 384 + print("\n◆ Reasoning") 385 + print(" ─────────") 386 + # Indent reasoning lines 387 + for line in chunk.reasoning.split('\n'): 388 + print(f" {line}") 372 389 else: 373 390 # Default log format (only when --reasoning is used due to log level) 374 - # Simple text format with underscores 375 - print("\nReasoning") 376 - print("_________") 377 - print(chunk.reasoning) 391 + # Format with Unicode characters 392 + print("\n◆ Reasoning") 393 + print(" ─────────") 394 + # Indent reasoning lines 395 + for line in chunk.reasoning.split('\n'): 396 + print(f" {line}") 378 397 elif chunk.message_type == 'tool_call_message': 379 398 # Parse tool arguments for better display 380 399 tool_name = chunk.tool_call.name ··· 385 404 # Extract the text being posted 386 405 text = args.get('text', '') 387 406 if text: 388 - # Simple text format with underscores 389 - print("\nBluesky Post") 390 - print("____________") 391 - print(text) 407 + # Format with Unicode characters 408 + print("\n✎ Bluesky Post") 409 + print(" ────────────") 410 + # Indent post text 411 + for line in text.split('\n'): 412 + print(f" {line}") 392 413 else: 393 414 log_with_panel(chunk.tool_call.arguments[:150] + "...", f"Tool call: {tool_name}", "blue") 394 415 elif tool_name == 'archival_memory_search': ··· 460 481 content = entry.get('content', '') 461 482 content_text += f"[{i}/{len(results)}] {timestamp}\n{content}\n\n" 462 483 463 - # Simple text format with underscores 484 + # Format with Unicode characters 464 485 title = f"{search_query} ({len(results)} results)" 465 - print(f"\n{title}") 466 - print("_" * len(title)) 467 - print(content_text.strip()) 486 + print(f"\n⚙ {title}") 487 + print(f" {'─' * len(title)}") 488 + # Indent content text 489 + for line in content_text.strip().split('\n'): 490 + print(f" {line}") 468 491 469 492 except Exception as e: 470 493 logger.error(f"Error formatting archival memory results: {e}") ··· 502 525 else: 503 526 logger.info(f"Tool result: {tool_name} - {status}") 504 527 elif chunk.message_type == 'assistant_message': 505 - # Simple text format with underscores 506 - print("\nAssistant Response") 507 - print("__________________") 508 - print(chunk.content) 528 + # Format with Unicode characters 529 + print("\n▶ Assistant Response") 530 + print(" ──────────────────") 531 + # Indent response text 532 + for line in chunk.content.split('\n'): 533 + print(f" {line}") 509 534 else: 510 535 # Filter out verbose message types 511 536 if chunk.message_type not in ['usage_statistics', 'stop_reason']: ··· 719 744 content = "\n\n".join([f"{j}. {msg}" for j, msg in enumerate(reply_messages, 1)]) 720 745 title = f"Reply Thread to @{author_handle} ({len(reply_messages)} messages)" 721 746 722 - # Simple text format with underscores 723 - print(f"\n{title}") 724 - print("_" * len(title)) 725 - print(content) 747 + # Format with Unicode characters 748 + print(f"\n✎ {title}") 749 + print(f" {'─' * len(title)}") 750 + # Indent content lines 751 + for line in content.split('\n'): 752 + print(f" {line}") 726 753 727 754 # Send the reply(s) with language (unless in testing mode) 728 755 if testing_mode: ··· 1213 1240 if args.simple_logs: 1214 1241 log_format = "void - %(levelname)s - %(message)s" 1215 1242 else: 1216 - log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 1217 - 1218 - # Reset logging configuration and apply new format 1219 - for handler in logging.root.handlers[:]: 1220 - logging.root.removeHandler(handler) 1221 - logging.basicConfig(level=logging.INFO, format=log_format, force=True) 1243 + # Create custom formatter with symbols 1244 + class SymbolFormatter(logging.Formatter): 1245 + """Custom formatter that adds symbols for different log levels""" 1246 + 1247 + SYMBOLS = { 1248 + logging.DEBUG: '◇', 1249 + logging.INFO: '✓', 1250 + logging.WARNING: '⚠', 1251 + logging.ERROR: '✗', 1252 + logging.CRITICAL: '‼' 1253 + } 1254 + 1255 + def format(self, record): 1256 + # Get the symbol for this log level 1257 + symbol = self.SYMBOLS.get(record.levelno, '•') 1258 + 1259 + # Format time as HH:MM:SS 1260 + timestamp = self.formatTime(record, "%H:%M:%S") 1261 + 1262 + # Build the formatted message 1263 + level_name = f"{record.levelname:<5}" # Left-align, 5 chars 1264 + 1265 + # Use vertical bar as separator 1266 + parts = [symbol, timestamp, '│', level_name, '│', record.getMessage()] 1267 + 1268 + return ' '.join(parts) 1269 + 1270 + # Reset logging configuration 1271 + for handler in logging.root.handlers[:]: 1272 + logging.root.removeHandler(handler) 1273 + 1274 + # Create handler with custom formatter 1275 + handler = logging.StreamHandler() 1276 + if not args.simple_logs: 1277 + handler.setFormatter(SymbolFormatter()) 1278 + else: 1279 + handler.setFormatter(logging.Formatter(log_format)) 1280 + 1281 + # Configure root logger 1282 + logging.root.setLevel(logging.INFO) 1283 + logging.root.addHandler(handler) 1222 1284 1223 1285 global logger, prompt_logger, console 1224 1286 logger = logging.getLogger("void_bot")