from typing import List, Dict, Any from ._shared import Cmdlet, CmdletArg from config import load_config, save_config CMDLET = Cmdlet( name=".config", summary="Manage configuration settings", usage=".config [key] [value]", args=[ CmdletArg( name="key", description="Configuration key to update (dot-separated)", required=False ), CmdletArg( name="value", description="New value for the configuration key", required=False ) ] ) def flatten_config(config: Dict[str, Any], parent_key: str = '', sep: str = '.') -> List[Dict[str, Any]]: items = [] for k, v in config.items(): if k.startswith('_'): # Skip internal keys continue new_key = f"{parent_key}{sep}{k}" if parent_key else k if isinstance(v, dict): items.extend(flatten_config(v, new_key, sep=sep)) else: items.append({ "Key": new_key, "Value": str(v), "Type": type(v).__name__, "_selection_args": [new_key] }) return items def set_nested_config(config: Dict[str, Any], key: str, value: str) -> bool: keys = key.split('.') d = config # Navigate to the parent dict for k in keys[:-1]: if k not in d or not isinstance(d[k], dict): d[k] = {} d = d[k] last_key = keys[-1] # Try to preserve type if key exists if last_key in d: current_val = d[last_key] if isinstance(current_val, bool): if value.lower() in ('true', 'yes', '1', 'on'): d[last_key] = True elif value.lower() in ('false', 'no', '0', 'off'): d[last_key] = False else: # Fallback to boolean conversion of string (usually True for non-empty) # But for config, explicit is better. print(f"Warning: Could not convert '{value}' to boolean. Using string.") d[last_key] = value elif isinstance(current_val, int): try: d[last_key] = int(value) except ValueError: print(f"Warning: Could not convert '{value}' to int. Using string.") d[last_key] = value elif isinstance(current_val, float): try: d[last_key] = float(value) except ValueError: print(f"Warning: Could not convert '{value}' to float. Using string.") d[last_key] = value else: d[last_key] = value else: # New key, try to infer type if value.lower() in ('true', 'false'): d[last_key] = (value.lower() == 'true') elif value.isdigit(): d[last_key] = int(value) else: d[last_key] = value return True def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int: # Reload config to ensure we have the latest on disk # We don't use the passed 'config' because we want to edit the file # and 'config' might contain runtime objects (like worker manager) # But load_config() returns a fresh dict from disk (or cache) # We should use load_config() current_config = load_config() # Parse args # We handle args manually because of the potential for spaces in values # and the @ expansion logic in CLI.py passing args if not args: # List mode items = flatten_config(current_config) # Sort by key items.sort(key=lambda x: x['Key']) # Emit items for ResultTable import pipeline as ctx for item in items: ctx.emit(item) return 0 # Update mode key = args[0] if len(args) < 2: print(f"Error: Value required for key '{key}'") return 1 value = " ".join(args[1:]) # Remove quotes if present if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")): value = value[1:-1] try: set_nested_config(current_config, key, value) save_config(current_config) print(f"Updated '{key}' to '{value}'") return 0 except Exception as e: print(f"Error updating config: {e}") return 1 CMDLET.exec = _run