Enhance user configuration management and logging

- Introduced user configuration command to set API key.
- Updated README and documentation for user config and logging paths.
- Refactored logging to support user-specific log files.
- Added tests for user configuration and logging behavior.
This commit is contained in:
2026-06-27 10:21:00 +08:00
parent 0e98ce57d4
commit 4685a0165d
10 changed files with 434 additions and 25 deletions

View File

@@ -5,6 +5,7 @@ import sys
import uvicorn
import nexus_claude_api.config as config
from nexus_claude_api.config import (
DEFAULT_ENDPOINT_URL,
DEFAULT_HOST,
@@ -13,7 +14,7 @@ from nexus_claude_api.config import (
DEFAULT_SMALL_MODEL,
Settings,
)
from nexus_claude_api.logging_config import configure_logging
from nexus_claude_api.logging_config import configure_logging, resolve_log_file
from nexus_claude_api.server import create_app
from nexus_claude_api.shell import generate_claude_code_powershell
@@ -31,11 +32,21 @@ def build_parser() -> argparse.ArgumentParser:
start.add_argument("--small-model", default=DEFAULT_SMALL_MODEL)
start.add_argument("--claude-code", action="store_true")
start.add_argument("--verbose", "-v", action="store_true")
start.add_argument(
"--dev",
action="store_true",
help="Use local development config and logs in the current directory.",
)
start.add_argument(
"--dry-run",
action="store_true",
help="Validate config and print helper output without starting the server.",
)
config = subparsers.add_parser("config", help="Manage user configuration")
config_subparsers = config.add_subparsers(dest="config_command")
config_set = config_subparsers.add_parser("set", help="Write user configuration")
config_set.add_argument("--api-key", required=True)
return parser
@@ -44,10 +55,21 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)
if args.command == "start":
return run_start(args)
if args.command == "config":
return run_config(args, parser)
parser.print_help()
return 0
def run_config(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
if args.config_command == "set":
config_path = config.write_user_config({"api_key": args.api_key})
print(f"Wrote user config to {config_path}")
return 0
parser.parse_args(["config", "--help"])
return 0
def run_start(args: argparse.Namespace) -> int:
settings = Settings.from_values(
host=args.host,
@@ -57,12 +79,15 @@ def run_start(args: argparse.Namespace) -> int:
model=args.model,
small_model=args.small_model,
verbose=args.verbose,
dev=args.dev,
)
if not settings.api_key:
print(
"Missing Nexus API key. Add nexus-claude-api.local.json, set "
"NEXUS_API_KEY or AWS_BEARER_TOKEN_BEDROCK, or pass --api-key.",
"Missing Nexus API key. Run "
"`nexus-claude-api config set --api-key <key>`, set NEXUS_API_KEY "
"or AWS_BEARER_TOKEN_BEDROCK, pass --api-key, or use --dev with "
"nexus-claude-api.local.json.",
file=sys.stderr,
)
return 2
@@ -74,7 +99,10 @@ def run_start(args: argparse.Namespace) -> int:
if args.dry_run:
return 0
log_file = configure_logging(verbose=settings.verbose)
log_file = configure_logging(
verbose=settings.verbose,
log_file=resolve_log_file(dev=args.dev),
)
print(f"Writing detailed logs to {log_file}")
app = create_app(settings)
uvicorn.run(

View File

@@ -13,6 +13,8 @@ DEFAULT_OPUS_MODEL = "claude-opus-4.6"
DEFAULT_MODEL = DEFAULT_OPUS_MODEL
DEFAULT_SMALL_MODEL = DEFAULT_OPUS_MODEL
LOCAL_CONFIG_FILENAME = "nexus-claude-api.local.json"
USER_CONFIG_DIR = Path.home() / ".config" / "nexus-claude-api"
USER_CONFIG_FILE = USER_CONFIG_DIR / "config.json"
MODEL_ID_MAP = {
@@ -48,12 +50,13 @@ class Settings:
small_model: str = DEFAULT_SMALL_MODEL,
verbose: bool = False,
require_api_key: bool = True,
dev: bool = False,
) -> "Settings":
local_config = load_local_config()
config = load_config(dev=dev)
resolved_api_key = (
api_key
or local_config.get("api_key")
or local_config.get("nexus_api_key")
or config.get("api_key")
or config.get("nexus_api_key")
or os.environ.get("NEXUS_API_KEY")
or os.environ.get("AWS_BEARER_TOKEN_BEDROCK")
)
@@ -78,6 +81,30 @@ def resolve_backend_model(model: str) -> str:
def load_local_config(path: str | Path = LOCAL_CONFIG_FILENAME) -> dict[str, str]:
return load_config_file(path)
def load_user_config(path: str | Path | None = None) -> dict[str, str]:
return load_config_file(path or USER_CONFIG_FILE)
def load_config(*, dev: bool = False) -> dict[str, str]:
if dev:
return load_local_config()
return load_user_config()
def write_user_config(config: dict[str, str], path: str | Path | None = None) -> Path:
config_path = Path(path or USER_CONFIG_FILE)
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(
json.dumps(config, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return config_path
def load_config_file(path: str | Path) -> dict[str, str]:
config_path = Path(path)
if not config_path.exists():
return {}

View File

@@ -3,9 +3,13 @@ from __future__ import annotations
import logging
from pathlib import Path
import nexus_claude_api.config as config
LOG_DIR = Path("logs")
LOG_FILE = LOG_DIR / "nexus-claude-api.log"
LOCAL_LOG_DIR = Path("logs")
LOCAL_LOG_FILE = LOCAL_LOG_DIR / "nexus-claude-api.log"
USER_LOG_DIR = config.USER_CONFIG_DIR / "logs"
USER_LOG_FILE = USER_LOG_DIR / "nexus-claude-api.log"
LOG_FILE = USER_LOG_FILE
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s: %(message)s"
@@ -24,3 +28,9 @@ def configure_logging(*, verbose: bool = False, log_file: Path = LOG_FILE) -> Pa
app_logger.propagate = False
return log_file
def resolve_log_file(*, dev: bool = False) -> Path:
if dev:
return LOCAL_LOG_FILE
return config.USER_CONFIG_DIR / "logs" / "nexus-claude-api.log"