"""Configuration management commands.""" import json import os from pathlib import Path import click from mycli.config import config from mycli.utils import handle_errors @click.group(name="config") def config_cmd(): """Manage mycli configuration.""" pass @config_cmd.command("show") def show(): """Show current configuration.""" config.show() @config_cmd.command("get") @click.argument("key") def get_value(key): """Get a configuration value by key (dot notation). Examples: mycli config get paths.research mycli config get ssh_hosts.tianhe.port mycli config get api_keys.deepseek """ value = config.get(key) if value is not None: if isinstance(value, dict): print(json.dumps(value, indent=2, ensure_ascii=False)) else: print(value) else: click.echo(f"Key not found: {key}", err=True) raise click.Exit(1) @config_cmd.command("set") @click.argument("key") @click.argument("value") @click.option("--json", "is_json", is_flag=True, help="Parse value as JSON") def set_value(key, value, is_json): """Set a configuration value. Examples: mycli config set paths.newpath ~/Documents/new mycli config set api_keys.newkey abc123 mycli config set --json ssh_hosts.newhost '{"host":"user@host","port":22}' """ # Load current config config_path = config._config_path with open(config_path) as f: data = json.load(f) # Parse value if is_json: try: parsed_value = json.loads(value) except json.JSONDecodeError as e: click.echo(f"Invalid JSON: {e}", err=True) raise click.Exit(1) else: parsed_value = value # Navigate to nested key keys = key.split(".") target = data for k in keys[:-1]: if k not in target: target[k] = {} target = target[k] # Set value target[keys[-1]] = parsed_value # Save with open(config_path, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") click.secho(f"Set {key} = {parsed_value}", fg="green") @config_cmd.command("delete") @click.argument("key") @click.option("--yes", "-y", is_flag=True, help="Skip confirmation") def delete_value(key, yes): """Delete a configuration key. Examples: mycli config delete api_keys.oldkey mycli config delete paths.oldpath """ # Check if exists if config.get(key) is None: click.echo(f"Key not found: {key}", err=True) raise click.Exit(1) if not yes: click.confirm(f"Delete '{key}'?", abort=True) # Load current config config_path = config._config_path with open(config_path) as f: data = json.load(f) # Navigate and delete keys = key.split(".") target = data for k in keys[:-1]: if k not in target: click.echo(f"Key not found: {key}", err=True) raise click.Exit(1) target = target[k] if keys[-1] in target: del target[keys[-1]] else: click.echo(f"Key not found: {key}", err=True) raise click.Exit(1) # Save with open(config_path, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") click.secho(f"Deleted {key}", fg="green") @config_cmd.command("list") def list_keys(): """List all top-level configuration keys.""" config_path = config._config_path with open(config_path) as f: data = json.load(f) click.secho("Configuration sections:", fg="cyan") for key in sorted(data.keys()): value = data[key] type_str = type(value).__name__ if isinstance(value, dict): count = len(value) click.echo(f" {key:20} ({type_str}, {count} items)") elif isinstance(value, list): click.echo(f" {key:20} ({type_str}, {len(value)} items)") else: click.echo(f" {key:20} ({type_str})") @config_cmd.command("paths") def list_paths(): """List all configured paths.""" paths = config.get_all_paths() max_len = max(len(name) for name in paths.keys()) for name, path in sorted(paths.items()): exists = "✓" if path.exists() else "✗" click.echo(f"{name:{max_len}} [{exists}] {path}") @config_cmd.command("edit") def edit(): """Open configuration file in editor.""" config.edit() @config_cmd.command("where") def where(): """Show configuration file location.""" click.echo(config._config_path) @config_cmd.command("init") def init_config(): """Create initial config from example template.""" config_path = config._config_path example_path = config_path.parent / "config.json.example" if config_path.exists(): click.echo(f"Config already exists: {config_path}", err=True) click.echo("Use 'mycli config edit' to modify it") raise click.Exit(1) if not example_path.exists(): click.echo(f"Example config not found: {example_path}", err=True) raise click.Exit(1) import shutil shutil.copy(example_path, config_path) click.secho(f"Created config from template: {config_path}", fg="green") click.echo("Edit it to add your API keys and customize paths")