Files
smartpeopleconnected_token-…/cli.py

278 lines
9.0 KiB
Python
Raw Normal View History

#!/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 <filename>", 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 <file> 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())