#!/bin/bash # VibeTunnel Logging Utility # Simplifies access to VibeTunnel logs using macOS unified logging system set -euo pipefail # Configuration SUBSYSTEM="com.steipete.clawdis" DEFAULT_LEVEL="info" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Function to handle sudo password errors handle_sudo_error() { echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${YELLOW}⚠️ Password Required for Log Access${NC}" echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" echo -e "vtlog needs to use sudo to show complete log data (Apple hides sensitive info by default)." echo -e "\nTo avoid password prompts, configure passwordless sudo for the log command:" echo -e "See: ${BLUE}apple/docs/logging-private-fix.md${NC}\n" echo -e "Quick fix:" echo -e " 1. Run: ${GREEN}sudo visudo${NC}" echo -e " 2. Add: ${GREEN}$(whoami) ALL=(ALL) NOPASSWD: /usr/bin/log${NC}" echo -e " 3. Save and exit (:wq)\n" echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" exit 1 } # Default values STREAM_MODE=false TIME_RANGE="5m" # Default to last 5 minutes CATEGORY="" LOG_LEVEL="$DEFAULT_LEVEL" SEARCH_TEXT="" OUTPUT_FILE="" ERRORS_ONLY=false SERVER_ONLY=false TAIL_LINES=50 # Default number of lines to show SHOW_TAIL=true SHOW_HELP=false # Function to show usage show_usage() { cat << EOF clawlog - Clawdis Logging Utility USAGE: vtlog [OPTIONS] DESCRIPTION: View Clawdis logs with full details (bypasses Apple's privacy redaction). Requires sudo access configured for /usr/bin/log command. LOG FLOW ARCHITECTURE: Clawdis logs flow through the macOS unified log (subsystem: com.steipete.clawdis). LOG CATEGORIES (examples): • voicewake - Voice wake detection/test harness • relay - Relay process manager • xpc - XPC service calls • notifications - Notification helper • screenshot - Screenshotter • shell - ShellRunner QUICK START: vtlog -n 100 Show last 100 lines from all components vtlog -f Follow logs in real-time vtlog -e Show only errors vtlog -c ServerManager Show logs from ServerManager only OPTIONS: -h, --help Show this help message -f, --follow Stream logs continuously (like tail -f) -n, --lines NUM Number of lines to show (default: 50) -l, --last TIME Time range to search (default: 5m) Examples: 5m, 1h, 2d, 1w -c, --category CAT Filter by category (e.g., ServerManager, SessionService) -e, --errors Show only error messages -d, --debug Show debug level logs (more verbose) -s, --search TEXT Search for specific text in log messages -o, --output FILE Export logs to file --server Show only server output logs --all Show all logs without tail limit --list-categories List all available log categories --json Output in JSON format EXAMPLES: vtlog Show last 50 lines from past 5 minutes (default) vtlog -f Stream logs continuously vtlog -n 100 Show last 100 lines vtlog -e Show only recent errors vtlog -l 30m -n 200 Show last 200 lines from past 30 minutes vtlog -c ServerManager Show recent ServerManager logs vtlog -s "fail" Search for "fail" in recent logs vtlog --server -e Show recent server errors vtlog -f -d Stream debug logs continuously CATEGORIES: Common categories include: - ServerManager - Server lifecycle and configuration - SessionService - Terminal session management - TerminalManager - Terminal spawning and control - GitRepository - Git integration features - ScreencapService - Screen capture functionality - WebRTCManager - WebRTC connections - UnixSocket - Unix socket communication - WindowTracker - Window tracking and focus - NgrokService - Ngrok tunnel management - ServerOutput - Node.js server output TIME FORMATS: - 5m = 5 minutes - 1h = 1 hour - 2d = 2 days - 1w = 1 week EOF } # Function to list categories list_categories() { echo -e "${BLUE}Fetching VibeTunnel log categories from the last hour...${NC}\n" # Get unique categories from recent logs log show --predicate "subsystem == \"$SUBSYSTEM\"" --last 1h 2>/dev/null | \ grep -E "category: \"[^\"]+\"" | \ sed -E 's/.*category: "([^"]+)".*/\1/' | \ sort | uniq | \ while read -r cat; do echo " • $cat" done echo -e "\n${YELLOW}Note: Only categories with recent activity are shown${NC}" } # Show help if no arguments provided if [[ $# -eq 0 ]]; then show_usage exit 0 fi # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_usage exit 0 ;; -f|--follow) STREAM_MODE=true SHOW_TAIL=false shift ;; -n|--lines) TAIL_LINES="$2" shift 2 ;; -l|--last) TIME_RANGE="$2" shift 2 ;; -c|--category) CATEGORY="$2" shift 2 ;; -e|--errors) ERRORS_ONLY=true shift ;; -d|--debug) LOG_LEVEL="debug" shift ;; -s|--search) SEARCH_TEXT="$2" shift 2 ;; -o|--output) OUTPUT_FILE="$2" shift 2 ;; --server) SERVER_ONLY=true CATEGORY="ServerOutput" shift ;; --list-categories) list_categories exit 0 ;; --json) STYLE_ARGS="--style json" shift ;; --all) SHOW_TAIL=false shift ;; *) echo -e "${RED}Unknown option: $1${NC}" echo "Use -h or --help for usage information" exit 1 ;; esac done # Build the predicate PREDICATE="subsystem == \"$SUBSYSTEM\"" # Add category filter if specified if [[ -n "$CATEGORY" ]]; then PREDICATE="$PREDICATE AND category == \"$CATEGORY\"" fi # Add error filter if specified if [[ "$ERRORS_ONLY" == true ]]; then PREDICATE="$PREDICATE AND (eventType == \"error\" OR messageType == \"error\" OR eventMessage CONTAINS \"ERROR\" OR eventMessage CONTAINS \"[31m\")" fi # Add search filter if specified if [[ -n "$SEARCH_TEXT" ]]; then PREDICATE="$PREDICATE AND eventMessage CONTAINS[c] \"$SEARCH_TEXT\"" fi # Build the command - always use sudo with --info to show private data if [[ "$STREAM_MODE" == true ]]; then # Streaming mode CMD="sudo log stream --predicate '$PREDICATE' --level $LOG_LEVEL --info" echo -e "${GREEN}Streaming VibeTunnel logs continuously...${NC}" echo -e "${YELLOW}Press Ctrl+C to stop${NC}\n" else # Show mode CMD="sudo log show --predicate '$PREDICATE'" # Add log level for show command if [[ "$LOG_LEVEL" == "debug" ]]; then CMD="$CMD --debug" else CMD="$CMD --info" fi # Add time range CMD="$CMD --last $TIME_RANGE" if [[ "$SHOW_TAIL" == true ]]; then echo -e "${GREEN}Showing last $TAIL_LINES log lines from the past $TIME_RANGE${NC}" else echo -e "${GREEN}Showing all logs from the past $TIME_RANGE${NC}" fi # Show applied filters if [[ "$ERRORS_ONLY" == true ]]; then echo -e "${RED}Filter: Errors only${NC}" fi if [[ -n "$CATEGORY" ]]; then echo -e "${BLUE}Category: $CATEGORY${NC}" fi if [[ -n "$SEARCH_TEXT" ]]; then echo -e "${YELLOW}Search: \"$SEARCH_TEXT\"${NC}" fi echo "" # Empty line for readability fi # Add style arguments if specified if [[ -n "${STYLE_ARGS:-}" ]]; then CMD="$CMD $STYLE_ARGS" fi # Execute the command if [[ -n "$OUTPUT_FILE" ]]; then # First check if sudo works without password for the log command if sudo -n /usr/bin/log show --last 1s 2>&1 | grep -q "password"; then handle_sudo_error fi echo -e "${BLUE}Exporting logs to: $OUTPUT_FILE${NC}\n" if [[ "$SHOW_TAIL" == true ]] && [[ "$STREAM_MODE" == false ]]; then eval "$CMD" 2>&1 | tail -n "$TAIL_LINES" > "$OUTPUT_FILE" else eval "$CMD" > "$OUTPUT_FILE" 2>&1 fi # Check if file was created and has content if [[ -s "$OUTPUT_FILE" ]]; then LINE_COUNT=$(wc -l < "$OUTPUT_FILE" | tr -d ' ') echo -e "${GREEN}✓ Exported $LINE_COUNT lines to $OUTPUT_FILE${NC}" else echo -e "${YELLOW}⚠ No logs found matching the criteria${NC}" fi else # Run interactively # First check if sudo works without password for the log command if sudo -n /usr/bin/log show --last 1s 2>&1 | grep -q "password"; then handle_sudo_error fi if [[ "$SHOW_TAIL" == true ]] && [[ "$STREAM_MODE" == false ]]; then # Apply tail for non-streaming mode eval "$CMD" 2>&1 | tail -n "$TAIL_LINES" echo -e "\n${YELLOW}Showing last $TAIL_LINES lines. Use --all or -n to see more.${NC}" else eval "$CMD" fi fi