Files
mytoolkit/mycli/commands/config_cmd.py
T
Zhengshou Lai bd167cfb6d feat: add API keys management and config CRUD operations
- Add api_keys and feishu_apps to config.json
- Add env export/init commands for shell integration
- Add config CRUD: list, set, delete, init
- Add config.json.example template
- Add config.json to .gitignore for security
- Update README with new commands

Usage:
  mycli env export              # Export all env vars
  mycli config set key value    # Set config value
  mycli config delete key       # Delete config key
2026-04-13 11:41:57 +08:00

199 lines
5.2 KiB
Python

"""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")