#!/usr/bin/env python3 """ Token Optimizer CLI Command-line interface for OpenClaw token optimization. """ import sys import json import argparse from pathlib import Path from src import __version__ def main(): parser = argparse.ArgumentParser( prog='token-optimizer', description='Reduce OpenClaw AI costs by 97%', epilog='For more info: https://github.com/smartpeopleconnected/openclaw-token-optimizer' ) parser.add_argument( '--no-color', action='store_true', help='Disable colored output' ) subparsers = parser.add_subparsers(dest='command', help='Available commands') # Analyze command subparsers.add_parser( 'analyze', help='Analyze current configuration and show optimization opportunities' ) # Optimize command optimize_parser = subparsers.add_parser( 'optimize', help='Apply token optimizations' ) optimize_parser.add_argument( '--mode', choices=['full', 'routing', 'heartbeat', 'caching', 'limits'], default='full', help='Optimization mode (default: full)' ) optimize_parser.add_argument( '--apply', action='store_true', help='Apply changes (default is dry-run preview)' ) # Verify command subparsers.add_parser( 'verify', help='Verify optimization setup and show estimated savings' ) # Setup heartbeat command heartbeat_parser = subparsers.add_parser( 'setup-heartbeat', help='Configure heartbeat provider (ollama, lmstudio, groq, none)' ) heartbeat_parser.add_argument( '--provider', choices=['ollama', 'lmstudio', 'groq', 'none'], default='ollama', help='Heartbeat provider (default: ollama)' ) heartbeat_parser.add_argument( '--model', default=None, help='Model name for heartbeat (default: provider-specific)' ) heartbeat_parser.add_argument( '--fallback', choices=['ollama', 'lmstudio', 'groq', 'none'], default=None, help='Fallback provider if primary is unavailable' ) heartbeat_parser.add_argument( '--apply', action='store_true', help='Apply changes (default is dry-run preview)' ) # Rollback command rollback_parser = subparsers.add_parser( 'rollback', help='Restore a previous configuration backup' ) rollback_parser.add_argument( '--list', action='store_true', dest='list_backups', help='List available backups' ) rollback_parser.add_argument( '--to', dest='backup_file', default=None, help='Restore a specific backup file' ) # Health command subparsers.add_parser( 'health', help='Quick system health check' ) # Version command subparsers.add_parser( 'version', help='Show version information' ) args = parser.parse_args() # Apply --no-color globally if args.no_color: import src.colors src.colors.NO_COLOR = True if args.command == 'analyze': from src.analyzer import main as analyze_main return analyze_main() elif args.command == 'optimize': from src.optimizer import TokenOptimizer dry_run = not args.apply if dry_run: from src.colors import Colors, colorize print(colorize("[DRY-RUN] Preview mode. Use --apply to make changes.\n", Colors.YELLOW)) optimizer = TokenOptimizer(dry_run=dry_run) optimizer.optimize_mode(args.mode) return 0 elif args.command == 'verify': from src.verify import main as verify_main return verify_main() elif args.command == 'setup-heartbeat': from src.optimizer import TokenOptimizer from src.colors import Colors, colorize dry_run = not args.apply if dry_run: print(colorize("[DRY-RUN] Preview mode. Use --apply to make changes.\n", Colors.YELLOW)) optimizer = TokenOptimizer(dry_run=dry_run) optimizer.setup_heartbeat_provider( provider=args.provider, model=args.model, fallback=args.fallback ) config = optimizer.load_config() config = optimizer.apply_heartbeat( config, provider=args.provider, model=args.model, fallback=args.fallback ) optimizer.save_config(config) return 0 elif args.command == 'rollback': from src.optimizer import TokenOptimizer from src.colors import Colors, colorize optimizer = TokenOptimizer() if args.list_backups: backups = optimizer.list_backups() if not backups: print(colorize("[INFO] No backups found", Colors.YELLOW)) else: print(colorize(f"[INFO] {len(backups)} backup(s) found:\n", Colors.CYAN)) for b in backups: size_kb = b.stat().st_size / 1024 print(f" {b.name} ({size_kb:.1f} KB)") print(colorize(f"\nRestore with: token-optimizer rollback --to ", Colors.CYAN)) return 0 if args.backup_file: backup_path = optimizer.backup_dir / args.backup_file if not backup_path.exists(): # Try as absolute path backup_path = Path(args.backup_file) success = optimizer.restore_backup(backup_path) return 0 if success else 1 print(colorize("[ERROR] Use --list to see backups or --to to restore", Colors.RED)) return 1 elif args.command == 'health': from src.colors import Colors, colorize from src.optimizer import resolve_heartbeat_provider, check_heartbeat_provider print(colorize("\n=== Token Optimizer - Health Check ===\n", Colors.BOLD + Colors.CYAN)) openclaw_dir = Path.home() / '.openclaw' config_path = openclaw_dir / 'openclaw.json' checks_passed = 0 checks_total = 0 # 1. Config exists checks_total += 1 if config_path.exists(): print(colorize("[PASS] Config file exists", Colors.GREEN)) checks_passed += 1 else: print(colorize("[FAIL] Config file not found", Colors.RED)) print(colorize(" Run: token-optimizer optimize", Colors.CYAN)) # 2. Valid JSON config = {} checks_total += 1 if config_path.exists(): try: with open(config_path, 'r') as f: config = json.load(f) print(colorize("[PASS] Config is valid JSON", Colors.GREEN)) checks_passed += 1 except (json.JSONDecodeError, IOError): print(colorize("[FAIL] Config is not valid JSON", Colors.RED)) else: print(colorize("[SKIP] Config not found, skipping JSON check", Colors.YELLOW)) # 3. Provider reachable checks_total += 1 provider = resolve_heartbeat_provider(config) if provider == "none": print(colorize("[SKIP] Heartbeat disabled (provider: none)", Colors.YELLOW)) checks_passed += 1 elif check_heartbeat_provider(provider): print(colorize(f"[PASS] Heartbeat provider '{provider}' is reachable", Colors.GREEN)) checks_passed += 1 else: print(colorize(f"[FAIL] Heartbeat provider '{provider}' is not reachable", Colors.RED)) # 4. Workspace lean checks_total += 1 workspace_dir = openclaw_dir / 'workspace' if workspace_dir.exists(): total_size = sum(f.stat().st_size for f in workspace_dir.iterdir() if f.is_file()) size_kb = total_size / 1024 if size_kb < 15: print(colorize(f"[PASS] Workspace is lean ({size_kb:.1f} KB)", Colors.GREEN)) checks_passed += 1 else: print(colorize(f"[WARN] Workspace is large ({size_kb:.1f} KB, target <15 KB)", Colors.YELLOW)) else: print(colorize("[SKIP] No workspace directory found", Colors.YELLOW)) checks_passed += 1 # 5. Budget active checks_total += 1 budgets = config.get('budgets', {}) if budgets.get('daily') or budgets.get('monthly'): print(colorize(f"[PASS] Budget controls active (daily: ${budgets.get('daily', 'n/a')}, monthly: ${budgets.get('monthly', 'n/a')})", Colors.GREEN)) checks_passed += 1 else: print(colorize("[FAIL] No budget controls configured", Colors.RED)) # Summary print(colorize(f"\n{checks_passed}/{checks_total} checks passed", Colors.BOLD)) return 0 if checks_passed == checks_total else 1 elif args.command == 'version': print(f"Token Optimizer v{__version__}") print("Reduce OpenClaw AI costs by 97%") return 0 else: parser.print_help() return 0 if __name__ == '__main__': sys.exit(main())