diff --git a/automation/completions.bash b/automation/completions.bash index 93a024c..8e2ceee 100644 --- a/automation/completions.bash +++ b/automation/completions.bash @@ -21,6 +21,18 @@ _claude_history() { COMPREPLY=($(compgen -W "--list --show --stats --unsummarized --all --limit" -- "${cur}")) } +_claude_log() { + local cur="${COMP_WORDS[COMP_CWORD]}" + + COMPREPLY=($(compgen -W "--list --tail --grep --since --type" -- "${cur}")) +} + +_claude_debug() { + local cur="${COMP_WORDS[COMP_CWORD]}" + + COMPREPLY=($(compgen -W "--full --json --paths" -- "${cur}")) +} + _claude_memory_add() { local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" @@ -52,6 +64,8 @@ complete -F _claude_memory_list memory-list.py complete -F _claude_restore restore.sh complete -F _claude_search search.py complete -F _claude_history history-browser.py +complete -F _claude_log log-viewer.py +complete -F _claude_debug debug.sh # Alias completions for convenience alias claude-validate='~/.claude/automation/validate-setup.sh' @@ -66,8 +80,9 @@ alias claude-history='python3 ~/.claude/automation/history-browser.py' alias claude-install='~/.claude/automation/install.sh' alias claude-test='~/.claude/automation/test-scripts.sh' alias claude-maintenance='~/.claude/automation/daily-maintenance.sh' +alias claude-log='python3 ~/.claude/automation/log-viewer.py' +alias claude-debug='~/.claude/automation/debug.sh' echo "Claude Code completions loaded. Available aliases:" -echo " claude-validate, claude-status, claude-backup, claude-restore, claude-clean" -echo " claude-memory-add, claude-memory-list, claude-search, claude-history" -echo " claude-install, claude-test, claude-maintenance" +echo " claude-{validate,status,backup,restore,clean,memory-add,memory-list}" +echo " claude-{search,history,install,test,maintenance,log,debug}" diff --git a/automation/completions.zsh b/automation/completions.zsh index 221dd91..924cd5b 100644 --- a/automation/completions.zsh +++ b/automation/completions.zsh @@ -64,12 +64,33 @@ _claude_history() { '--limit[Limit results]:count:' } +# Log viewer completion +_claude_log() { + _arguments \ + '--list[List log files]' \ + '--tail[Number of lines]:count:' \ + '--grep[Filter pattern]:pattern:' \ + '--since[Since date]:date:' \ + '--type[Log type]:type:' \ + '*:file:' +} + +# Debug completion +_claude_debug() { + _arguments \ + '--full[Full debug report]' \ + '--json[JSON output]' \ + '--paths[Show paths only]' +} + # Register completions compdef _memory_add memory-add.py compdef _memory_list memory-list.py compdef _claude_restore restore.sh compdef _claude_search search.py compdef _claude_history history-browser.py +compdef _claude_log log-viewer.py +compdef _claude_debug debug.sh # Aliases alias claude-validate='~/.claude/automation/validate-setup.sh' @@ -84,7 +105,9 @@ alias claude-history='python3 ~/.claude/automation/history-browser.py' alias claude-install='~/.claude/automation/install.sh' alias claude-test='~/.claude/automation/test-scripts.sh' alias claude-maintenance='~/.claude/automation/daily-maintenance.sh' +alias claude-log='python3 ~/.claude/automation/log-viewer.py' +alias claude-debug='~/.claude/automation/debug.sh' echo "Claude Code completions loaded (zsh)" echo " Aliases: claude-{validate,status,backup,restore,clean,memory-add,memory-list}" -echo " claude-{search,history,install,test,maintenance}" +echo " claude-{search,history,install,test,maintenance,log,debug}" diff --git a/automation/debug.sh b/automation/debug.sh new file mode 100755 index 0000000..6270af2 --- /dev/null +++ b/automation/debug.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# Debug script for troubleshooting Claude Code configuration +# Usage: ./debug.sh [--full|--json|--paths] + +set -euo pipefail + +CLAUDE_DIR="${HOME}/.claude" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${BLUE}ℹ${NC} $1"; } +ok() { echo -e "${GREEN}āœ“${NC} $1"; } +warn() { echo -e "${YELLOW}⚠${NC} $1"; } +error() { echo -e "${RED}āœ—${NC} $1"; } + +show_paths() { + echo "" + echo "=== Configuration Paths ===" + echo "" + echo "CLAUDE_DIR: ${CLAUDE_DIR}" + echo "" + echo "Key files:" + echo " CLAUDE.md: ${CLAUDE_DIR}/CLAUDE.md" + echo " plugin.json: ${CLAUDE_DIR}/.claude-plugin/plugin.json" + echo " hooks.json: ${CLAUDE_DIR}/hooks/hooks.json" + echo " registry.json: ${CLAUDE_DIR}/state/component-registry.json" + echo "" + echo "Memory:" + echo " preferences: ${CLAUDE_DIR}/state/personal-assistant/memory/preferences.json" + echo " decisions: ${CLAUDE_DIR}/state/personal-assistant/memory/decisions.json" + echo " history index: ${CLAUDE_DIR}/state/personal-assistant/history/index.json" + echo "" + echo "Logs:" + echo " maintenance: ${CLAUDE_DIR}/logs/maintenance-*.log" + echo "" +} + +check_file() { + local path="$1" + local name="$2" + if [[ -f "$path" ]]; then + local size=$(stat -c %s "$path" 2>/dev/null || stat -f %z "$path" 2>/dev/null || echo "?") + ok "$name ($size bytes)" + return 0 + else + error "$name (missing)" + return 1 + fi +} + +check_json() { + local path="$1" + local name="$2" + if [[ -f "$path" ]]; then + if python3 -c "import json; json.load(open('$path'))" 2>/dev/null; then + ok "$name (valid JSON)" + return 0 + else + error "$name (invalid JSON)" + return 1 + fi + else + warn "$name (file missing)" + return 1 + fi +} + +check_script() { + local path="$1" + local name="$2" + if [[ -f "$path" ]]; then + if [[ -x "$path" ]]; then + ok "$name (executable)" + else + warn "$name (not executable)" + fi + else + error "$name (missing)" + fi +} + +show_summary() { + echo "" + echo "šŸ” Claude Code Debug Report" + echo " Generated: $(date)" + echo "" + + # Version + echo "=== Version ===" + if [[ -f "${CLAUDE_DIR}/VERSION" ]]; then + echo " Version: $(cat "${CLAUDE_DIR}/VERSION")" + else + echo " Version: unknown" + fi + echo "" + + # Core files + echo "=== Core Files ===" + check_file "${CLAUDE_DIR}/CLAUDE.md" "CLAUDE.md" + check_json "${CLAUDE_DIR}/.claude-plugin/plugin.json" "plugin.json" + check_json "${CLAUDE_DIR}/hooks/hooks.json" "hooks.json" + check_json "${CLAUDE_DIR}/state/component-registry.json" "component-registry.json" + echo "" + + # State files + echo "=== State Files ===" + check_json "${CLAUDE_DIR}/state/personal-assistant-preferences.json" "PA preferences" + check_json "${CLAUDE_DIR}/state/personal-assistant/session-context.json" "Session context" + check_json "${CLAUDE_DIR}/state/personal-assistant/general-instructions.json" "General instructions" + check_json "${CLAUDE_DIR}/state/sysadmin/session-autonomy.json" "Sysadmin autonomy" + echo "" + + # Memory + echo "=== Memory Files ===" + check_json "${CLAUDE_DIR}/state/personal-assistant/memory/preferences.json" "Memory: preferences" + check_json "${CLAUDE_DIR}/state/personal-assistant/memory/decisions.json" "Memory: decisions" + check_json "${CLAUDE_DIR}/state/personal-assistant/memory/projects.json" "Memory: projects" + check_json "${CLAUDE_DIR}/state/personal-assistant/memory/facts.json" "Memory: facts" + check_json "${CLAUDE_DIR}/state/personal-assistant/history/index.json" "History index" + echo "" + + # Scripts + echo "=== Key Scripts ===" + check_script "${CLAUDE_DIR}/automation/validate-setup.sh" "validate-setup.sh" + check_script "${CLAUDE_DIR}/automation/quick-status.sh" "quick-status.sh" + check_script "${CLAUDE_DIR}/automation/backup.sh" "backup.sh" + check_script "${CLAUDE_DIR}/automation/install.sh" "install.sh" + check_script "${CLAUDE_DIR}/hooks/scripts/session-start.sh" "session-start.sh" + echo "" + + # Skills + echo "=== Skills ===" + skill_count=$(find "${CLAUDE_DIR}/skills" -name "SKILL.md" 2>/dev/null | wc -l) + echo " Skills found: $skill_count" + find "${CLAUDE_DIR}/skills" -name "SKILL.md" -exec dirname {} \; 2>/dev/null | while read dir; do + echo " - $(basename "$dir")" + done + echo "" + + # Commands + echo "=== Commands ===" + cmd_count=$(find "${CLAUDE_DIR}/commands" -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + echo " Commands found: $cmd_count" + echo "" + + # Agents + echo "=== Agents ===" + agent_count=$(find "${CLAUDE_DIR}/agents" -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + echo " Agent files found: $agent_count" + echo "" + + # Environment + echo "=== Environment ===" + echo " HOME: $HOME" + echo " USER: ${USER:-$(whoami)}" + echo " Shell: ${SHELL:-unknown}" + echo " Python: $(python3 --version 2>&1 || echo 'not found')" + if command -v kubectl &> /dev/null; then + ok "kubectl installed" + else + warn "kubectl not found" + fi + echo "" + + # Disk usage + echo "=== Disk Usage ===" + total=$(du -sh "${CLAUDE_DIR}" 2>/dev/null | cut -f1) + echo " Total: $total" + echo "" + echo " By directory:" + du -sh "${CLAUDE_DIR}"/{agents,automation,commands,hooks,mcp,skills,state,workflows,logs,backups} 2>/dev/null | while read size dir; do + echo " $(basename "$dir"): $size" + done + echo "" +} + +show_json() { + echo "{" + echo " \"version\": \"$(cat "${CLAUDE_DIR}/VERSION" 2>/dev/null || echo 'unknown')\"," + echo " \"timestamp\": \"$(date -Iseconds)\"," + echo " \"paths\": {" + echo " \"claude_dir\": \"${CLAUDE_DIR}\"," + echo " \"home\": \"$HOME\"" + echo " }," + + # Count components + skill_count=$(find "${CLAUDE_DIR}/skills" -name "SKILL.md" 2>/dev/null | wc -l | tr -d ' ') + cmd_count=$(find "${CLAUDE_DIR}/commands" -name "*.md" ! -name "README.md" 2>/dev/null | wc -l | tr -d ' ') + agent_count=$(find "${CLAUDE_DIR}/agents" -name "*.md" ! -name "README.md" 2>/dev/null | wc -l | tr -d ' ') + + echo " \"components\": {" + echo " \"skills\": $skill_count," + echo " \"commands\": $cmd_count," + echo " \"agents\": $agent_count" + echo " }," + + # Check files + files_ok=true + for f in CLAUDE.md .claude-plugin/plugin.json hooks/hooks.json state/component-registry.json; do + if [[ ! -f "${CLAUDE_DIR}/$f" ]]; then + files_ok=false + break + fi + done + + echo " \"status\": {" + echo " \"core_files\": $files_ok," + echo " \"python\": $(python3 --version &>/dev/null && echo true || echo false)," + echo " \"kubectl\": $(command -v kubectl &>/dev/null && echo true || echo false)" + echo " }" + echo "}" +} + +# Parse arguments +case "${1:-}" in + --paths) + show_paths + ;; + --json) + show_json + ;; + --full|*) + show_summary + ;; +esac diff --git a/automation/log-viewer.py b/automation/log-viewer.py new file mode 100755 index 0000000..9cc0350 --- /dev/null +++ b/automation/log-viewer.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +""" +View and analyze Claude Code logs. +Usage: python3 log-viewer.py [--tail N] [--grep PATTERN] [--since DATE] [--type TYPE] +""" + +import argparse +import json +import os +import re +import sys +from datetime import datetime, timedelta +from pathlib import Path +from typing import List, Optional + +CLAUDE_DIR = Path.home() / ".claude" +LOG_DIR = CLAUDE_DIR / "logs" + + +def get_log_files(log_type: Optional[str] = None) -> List[Path]: + """Get list of log files, optionally filtered by type.""" + if not LOG_DIR.exists(): + return [] + + files = list(LOG_DIR.glob("*.log")) + + if log_type: + files = [f for f in files if log_type in f.name] + + return sorted(files, key=lambda f: f.stat().st_mtime, reverse=True) + + +def parse_log_line(line: str) -> Optional[dict]: + """Parse a log line into structured data.""" + # Try JSON format + try: + return json.loads(line) + except json.JSONDecodeError: + pass + + # Try timestamp format: [YYYY-MM-DD HH:MM:SS] message + match = re.match(r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (.+)', line) + if match: + return { + "timestamp": match.group(1), + "message": match.group(2) + } + + # Plain text + return {"message": line.strip()} if line.strip() else None + + +def filter_by_date(entries: List[dict], since: str) -> List[dict]: + """Filter entries by date.""" + try: + since_date = datetime.strptime(since, "%Y-%m-%d") + except ValueError: + try: + # Try relative (e.g., "1d", "7d", "1h") + if since.endswith("d"): + days = int(since[:-1]) + since_date = datetime.now() - timedelta(days=days) + elif since.endswith("h"): + hours = int(since[:-1]) + since_date = datetime.now() - timedelta(hours=hours) + else: + return entries + except ValueError: + return entries + + result = [] + for entry in entries: + ts = entry.get("timestamp", "") + if ts: + try: + entry_date = datetime.strptime(ts[:19], "%Y-%m-%d %H:%M:%S") + if entry_date >= since_date: + result.append(entry) + except ValueError: + result.append(entry) # Include if can't parse + else: + result.append(entry) + + return result + + +def grep_entries(entries: List[dict], pattern: str) -> List[dict]: + """Filter entries by grep pattern.""" + regex = re.compile(pattern, re.IGNORECASE) + result = [] + for entry in entries: + message = entry.get("message", "") + if regex.search(message): + result.append(entry) + return result + + +def tail_file(path: Path, n: int = 50) -> List[str]: + """Get last N lines from a file.""" + try: + with open(path) as f: + lines = f.readlines() + return lines[-n:] + except Exception: + return [] + + +def format_entry(entry: dict) -> str: + """Format a log entry for display.""" + ts = entry.get("timestamp", "") + msg = entry.get("message", "") + + if ts: + return f"[{ts}] {msg}" + return msg + + +def list_logs(): + """List available log files.""" + files = get_log_files() + + if not files: + print("No log files found.") + return + + print(f"\nšŸ“‹ Log Files ({len(files)})\n") + print(f"{'File':<40} {'Size':<10} {'Modified'}") + print("-" * 70) + + for f in files: + stat = f.stat() + size = stat.st_size + if size > 1024 * 1024: + size_str = f"{size / 1024 / 1024:.1f}M" + elif size > 1024: + size_str = f"{size / 1024:.1f}K" + else: + size_str = f"{size}B" + + mtime = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M") + print(f"{f.name:<40} {size_str:<10} {mtime}") + + print("") + + +def view_log(filename: str, tail: int = 50, grep: Optional[str] = None, + since: Optional[str] = None): + """View a specific log file.""" + # Find the log file + log_path = LOG_DIR / filename + if not log_path.exists(): + # Try with .log extension + log_path = LOG_DIR / f"{filename}.log" + + if not log_path.exists(): + print(f"Log file not found: {filename}") + return + + lines = tail_file(log_path, tail * 2 if grep else tail) # Get more if filtering + + entries = [] + for line in lines: + entry = parse_log_line(line) + if entry: + entries.append(entry) + + if since: + entries = filter_by_date(entries, since) + + if grep: + entries = grep_entries(entries, grep) + + # Limit to tail after filtering + entries = entries[-tail:] + + print(f"\nšŸ“œ {log_path.name} (last {len(entries)} entries)\n") + print("-" * 70) + + for entry in entries: + print(format_entry(entry)) + + print("") + + +def main(): + parser = argparse.ArgumentParser(description="View Claude Code logs") + parser.add_argument("file", nargs="?", help="Log file to view") + parser.add_argument("--list", "-l", action="store_true", help="List log files") + parser.add_argument("--tail", "-n", type=int, default=50, + help="Number of lines to show (default: 50)") + parser.add_argument("--grep", "-g", type=str, help="Filter by pattern") + parser.add_argument("--since", "-s", type=str, + help="Show entries since date (YYYY-MM-DD or 1d/7d/1h)") + parser.add_argument("--type", "-t", type=str, + help="Filter log files by type (maintenance, etc.)") + + args = parser.parse_args() + + if args.list: + list_logs() + elif args.file: + view_log(args.file, tail=args.tail, grep=args.grep, since=args.since) + else: + # Default: show most recent log + files = get_log_files(args.type) + if files: + view_log(files[0].name, tail=args.tail, grep=args.grep, since=args.since) + else: + print("No log files found. Run some automation scripts first.") + + +if __name__ == "__main__": + main() diff --git a/automation/test-scripts.sh b/automation/test-scripts.sh index 88c845f..4baf327 100755 --- a/automation/test-scripts.sh +++ b/automation/test-scripts.sh @@ -71,6 +71,13 @@ else fail "history-browser.py syntax error" fi +# Test 8: log-viewer.py +if python3 -m py_compile "${AUTOMATION_DIR}/log-viewer.py" 2>/dev/null; then + pass "log-viewer.py syntax valid" +else + fail "log-viewer.py syntax error" +fi + echo "" echo "=== Skill Scripts ===" @@ -115,7 +122,7 @@ else fi # Test automation bash scripts -for script in install.sh daily-maintenance.sh backup.sh restore.sh clean.sh; do +for script in install.sh daily-maintenance.sh backup.sh restore.sh clean.sh debug.sh; do if [[ -f "${AUTOMATION_DIR}/${script}" ]]; then if bash -n "${AUTOMATION_DIR}/${script}" 2>/dev/null; then pass "${script} syntax valid" diff --git a/commands/README.md b/commands/README.md index c4f0993..6493f8e 100644 --- a/commands/README.md +++ b/commands/README.md @@ -15,6 +15,8 @@ Slash commands for quick actions. User-invoked (type `/command` to trigger). | `/remember` | `/save`, `/note` | Quick save to memory | | `/config` | `/settings`, `/prefs` | View/manage configuration | | `/search` | `/find`, `/lookup` | Search memory, history, config | +| `/log` | `/logs`, `/logview` | View and analyze logs | +| `/debug` | `/diag`, `/diagnose` | Debug and troubleshoot config | | `/maintain` | `/maintenance`, `/admin` | Configuration maintenance | | `/programmer` | | Code development tasks | | `/gcal` | `/calendar`, `/cal` | Google Calendar access | diff --git a/commands/debug.md b/commands/debug.md new file mode 100644 index 0000000..bc38f6e --- /dev/null +++ b/commands/debug.md @@ -0,0 +1,48 @@ +--- +name: debug +description: Debug and troubleshoot Claude Code configuration +aliases: [diag, diagnose] +invokes: skill:debug +--- + +# Debug Command + +Generate a debug report for troubleshooting. + +## Usage + +``` +/debug # Full debug report +/debug --paths # Show configuration paths +/debug --json # Output as JSON +``` + +## Implementation + +Run the debug script: + +```bash +~/.claude/automation/debug.sh [options] +``` + +## Report Sections + +| Section | Contents | +|---------|----------| +| Version | Current configuration version | +| Core Files | Status of essential files | +| State Files | PA preferences, session context, autonomy | +| Memory Files | Preferences, decisions, projects, facts | +| Key Scripts | Executable status of automation scripts | +| Skills | List of available skills | +| Commands | Count of slash commands | +| Agents | Count of agent files | +| Environment | Shell, Python, kubectl status | +| Disk Usage | Space used by each directory | + +## JSON Output + +Use `--json` for machine-readable output, useful for: +- Automated health checks +- Monitoring integration +- Scripting diff --git a/commands/log.md b/commands/log.md new file mode 100644 index 0000000..16cfa30 --- /dev/null +++ b/commands/log.md @@ -0,0 +1,39 @@ +--- +name: log +description: View and analyze Claude Code logs +aliases: [logs, logview] +invokes: skill:log-viewer +--- + +# Log Command + +View and analyze automation logs. + +## Usage + +``` +/log # Show most recent log +/log --list # List all log files +/log # View specific log +/log --tail 100 # Show last 100 lines +/log --grep "error" # Filter by pattern +/log --since 1d # Show entries from last day +``` + +## Implementation + +Run the log viewer script: + +```bash +python3 ~/.claude/automation/log-viewer.py [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `--list` | List available log files | +| `--tail N` | Number of lines to show (default: 50) | +| `--grep PATTERN` | Filter by regex pattern | +| `--since DATE` | Filter by date (YYYY-MM-DD or 1d/7d/1h) | +| `--type TYPE` | Filter log files by type | diff --git a/state/component-registry.json b/state/component-registry.json index b0e62e7..8ca176e 100644 --- a/state/component-registry.json +++ b/state/component-registry.json @@ -164,6 +164,16 @@ "description": "Search memory, history, and configuration", "aliases": ["/find", "/lookup"], "invokes": "command:search" + }, + "/log": { + "description": "View and analyze logs", + "aliases": ["/logs", "/logview"], + "invokes": "command:log" + }, + "/debug": { + "description": "Debug and troubleshoot configuration", + "aliases": ["/diag", "/diagnose"], + "invokes": "command:debug" } }, "agents": { @@ -292,7 +302,10 @@ "memory-add": "~/.claude/automation/memory-add.py", "memory-list": "~/.claude/automation/memory-list.py", "search": "~/.claude/automation/search.py", - "history-browser": "~/.claude/automation/history-browser.py" + "history-browser": "~/.claude/automation/history-browser.py", + "log-viewer": "~/.claude/automation/log-viewer.py", + "debug": "~/.claude/automation/debug.sh", + "daily-maintenance": "~/.claude/automation/daily-maintenance.sh" }, "completions": { "bash": "~/.claude/automation/completions.bash",