文件预览

patch_color.py

查看 Clawd Modifier 技能包中的文件内容。

文件内容

scripts/patch_color.py

#!/usr/bin/env python3
"""
Patch Clawd's color in Claude Code cli.js

Usage:
    python patch_color.py <color>              # Use preset color name
    python patch_color.py --rgb R,G,B          # Use custom RGB
    python patch_color.py --restore            # Restore original color
    python patch_color.py --list               # List preset colors

Examples:
    python patch_color.py blue
    python patch_color.py --rgb 100,200,150
    python patch_color.py gold --cli-path /custom/path/cli.js
"""

import argparse
import re
import shutil
import sys
from pathlib import Path
from datetime import datetime

DEFAULT_CLI_PATH = "/opt/node22/lib/node_modules/@anthropic-ai/claude-code/cli.js"

# Original Clawd color
ORIGINAL_COLOR = {
    'rgb': 'rgb(215,119,87)',
    'ansi': 'ansi:redBright',
}

# Preset color palettes
PRESETS = {
    'original': {'rgb': 'rgb(215,119,87)', 'ansi': 'ansi:redBright'},
    'blue': {'rgb': 'rgb(100,149,237)', 'ansi': 'ansi:blueBright'},
    'green': {'rgb': 'rgb(119,215,87)', 'ansi': 'ansi:greenBright'},
    'purple': {'rgb': 'rgb(180,119,215)', 'ansi': 'ansi:magentaBright'},
    'gold': {'rgb': 'rgb(255,215,0)', 'ansi': 'ansi:yellowBright'},
    'cyan': {'rgb': 'rgb(0,215,215)', 'ansi': 'ansi:cyanBright'},
    'pink': {'rgb': 'rgb(255,105,180)', 'ansi': 'ansi:magentaBright'},
    'orange': {'rgb': 'rgb(255,165,0)', 'ansi': 'ansi:yellowBright'},
    'lime': {'rgb': 'rgb(50,205,50)', 'ansi': 'ansi:greenBright'},
    'red': {'rgb': 'rgb(255,69,0)', 'ansi': 'ansi:redBright'},
    'white': {'rgb': 'rgb(255,255,255)', 'ansi': 'ansi:whiteBright'},
    'christmas_red': {'rgb': 'rgb(165,42,42)', 'ansi': 'ansi:red'},
    'christmas_green': {'rgb': 'rgb(34,139,34)', 'ansi': 'ansi:green'},
    'halloween_orange': {'rgb': 'rgb(255,117,24)', 'ansi': 'ansi:yellowBright'},
    'valentines_pink': {'rgb': 'rgb(255,20,147)', 'ansi': 'ansi:magentaBright'},
}

def find_cli_js():
    """Find cli.js in common locations."""
    paths = [
        DEFAULT_CLI_PATH,
        Path.home() / ".npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js",
        "/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js",
    ]
    for p in paths:
        if Path(p).exists():
            return str(p)
    return None

def backup_cli(cli_path: str) -> str:
    """Create a backup of cli.js."""
    backup_path = f"{cli_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
    shutil.copy2(cli_path, backup_path)
    return backup_path

def patch_color(cli_path: str, rgb: str, ansi: str, dry_run: bool = False) -> dict:
    """Patch Clawd's body color in cli.js."""
    code = Path(cli_path).read_text()

    # Find and replace RGB colors (clawd_body only)
    old_rgb_pattern = r'clawd_body:"rgb\([^)]+\)"'
    new_rgb = f'clawd_body:"{rgb}"'

    # Find and replace ANSI colors
    old_ansi_pattern = r'clawd_body:"ansi:[^"]+"'
    new_ansi = f'clawd_body:"{ansi}"'

    rgb_count = len(re.findall(old_rgb_pattern, code))
    ansi_count = len(re.findall(old_ansi_pattern, code))

    if rgb_count == 0 and ansi_count == 0:
        return {'success': False, 'error': 'No clawd_body colors found in cli.js'}

    new_code = re.sub(old_rgb_pattern, new_rgb, code)
    new_code = re.sub(old_ansi_pattern, new_ansi, new_code)

    if not dry_run:
        Path(cli_path).write_text(new_code)

    return {
        'success': True,
        'rgb_replacements': rgb_count,
        'ansi_replacements': ansi_count,
        'new_rgb': rgb,
        'new_ansi': ansi,
        'dry_run': dry_run,
    }

def main():
    parser = argparse.ArgumentParser(description="Modify Clawd's color in Claude Code")
    parser.add_argument('preset', nargs='?', help='Preset color name')
    parser.add_argument('--rgb', help='Custom RGB color (e.g., 100,149,237)')
    parser.add_argument('--ansi', default=None, help='ANSI color override')
    parser.add_argument('--cli-path', default=None, help='Path to cli.js')
    parser.add_argument('--restore', action='store_true', help='Restore original color')
    parser.add_argument('--list', action='store_true', help='List preset colors')
    parser.add_argument('--dry-run', action='store_true', help='Show what would change')
    parser.add_argument('--no-backup', action='store_true', help='Skip backup')

    args = parser.parse_args()

    if args.list:
        print("Available preset colors:")
        for name, colors in PRESETS.items():
            print(f"  {name:20} {colors['rgb']}")
        return

    cli_path = args.cli_path or find_cli_js()
    if not cli_path or not Path(cli_path).exists():
        print(f"Error: cli.js not found. Use --cli-path to specify.", file=sys.stderr)
        sys.exit(1)

    # Determine target color
    if args.restore:
        rgb, ansi = ORIGINAL_COLOR['rgb'], ORIGINAL_COLOR['ansi']
    elif args.rgb:
        parts = args.rgb.split(',')
        if len(parts) != 3:
            print("Error: RGB must be in format R,G,B (e.g., 100,149,237)", file=sys.stderr)
            sys.exit(1)
        rgb = f"rgb({args.rgb})"
        ansi = args.ansi or 'ansi:whiteBright'
    elif args.preset:
        if args.preset not in PRESETS:
            print(f"Error: Unknown preset '{args.preset}'. Use --list to see options.", file=sys.stderr)
            sys.exit(1)
        rgb = PRESETS[args.preset]['rgb']
        ansi = PRESETS[args.preset]['ansi']
    else:
        parser.print_help()
        return

    # Backup unless disabled
    if not args.no_backup and not args.dry_run:
        backup = backup_cli(cli_path)
        print(f"Backup created: {backup}")

    # Apply patch
    result = patch_color(cli_path, rgb, ansi, dry_run=args.dry_run)

    if result['success']:
        action = "Would patch" if args.dry_run else "Patched"
        print(f"{action} {result['rgb_replacements']} RGB and {result['ansi_replacements']} ANSI color definitions")
        print(f"New color: {result['new_rgb']} / {result['new_ansi']}")
    else:
        print(f"Error: {result['error']}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()