def api(
# Configuration
config: Annotated[
Path | None,
typer.Option(
"--config",
"-c",
help="Path to configuration file (TOML, JSON, or YAML)",
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
rich_help_panel="Configuration",
),
] = None,
# Server options
port: Annotated[
int | None,
typer.Option(
"--port",
"-p",
help="Port to run the server on",
callback=validate_port,
rich_help_panel="Server Settings",
),
] = None,
host: Annotated[
str | None,
typer.Option(
"--host",
"-h",
help="Host to bind the server to",
rich_help_panel="Server Settings",
),
] = None,
reload: Annotated[
bool | None,
typer.Option(
"--reload/--no-reload",
help="Enable auto-reload for development",
rich_help_panel="Server Settings",
),
] = None,
log_level: Annotated[
str | None,
typer.Option(
"--log-level",
help="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
callback=validate_log_level,
rich_help_panel="Server Settings",
),
] = None,
log_file: Annotated[
str | None,
typer.Option(
"--log-file",
help="Path to JSON log file. If specified, logs will be written to this file in JSON format",
rich_help_panel="Server Settings",
),
] = None,
# Security options
auth_token: Annotated[
str | None,
typer.Option(
"--auth-token",
help="Bearer token for API authentication",
callback=validate_auth_token,
rich_help_panel="Security Settings",
),
] = None,
# Claude options
max_thinking_tokens: Annotated[
int | None,
typer.Option(
"--max-thinking-tokens",
help="Maximum thinking tokens for Claude Code",
callback=validate_max_thinking_tokens,
rich_help_panel="Claude Settings",
),
] = None,
allowed_tools: Annotated[
str | None,
typer.Option(
"--allowed-tools",
help="List of allowed tools (comma-separated)",
rich_help_panel="Claude Settings",
),
] = None,
disallowed_tools: Annotated[
str | None,
typer.Option(
"--disallowed-tools",
help="List of disallowed tools (comma-separated)",
rich_help_panel="Claude Settings",
),
] = None,
claude_cli_path: Annotated[
str | None,
typer.Option(
"--claude-cli-path",
help="Path to Claude CLI executable",
callback=validate_claude_cli_path,
rich_help_panel="Claude Settings",
),
] = None,
append_system_prompt: Annotated[
str | None,
typer.Option(
"--append-system-prompt",
help="Additional system prompt to append",
rich_help_panel="Claude Settings",
),
] = None,
permission_mode: Annotated[
str | None,
typer.Option(
"--permission-mode",
help="Permission mode: default, acceptEdits, or bypassPermissions",
callback=validate_permission_mode,
rich_help_panel="Claude Settings",
),
] = None,
max_turns: Annotated[
int | None,
typer.Option(
"--max-turns",
help="Maximum conversation turns",
callback=validate_max_turns,
rich_help_panel="Claude Settings",
),
] = None,
cwd: Annotated[
str | None,
typer.Option(
"--cwd",
help="Working directory path",
callback=validate_cwd,
rich_help_panel="Claude Settings",
),
] = None,
permission_prompt_tool_name: Annotated[
str | None,
typer.Option(
"--permission-prompt-tool-name",
help="Permission prompt tool name",
rich_help_panel="Claude Settings",
),
] = None,
# Core settings
docker: Annotated[
bool,
typer.Option(
"--docker",
"-d",
help="Run API server using Docker instead of local execution",
),
] = False,
# Docker settings using shared parameters
docker_image: Annotated[
str | None,
typer.Option(
"--docker-image",
help="Docker image to use (overrides configuration)",
rich_help_panel="Docker Settings",
),
] = None,
docker_env: Annotated[
list[str] | None,
typer.Option(
"--docker-env",
"-e",
help="Environment variables to pass to Docker container",
rich_help_panel="Docker Settings",
),
] = None,
docker_volume: Annotated[
list[str] | None,
typer.Option(
"--docker-volume",
"-v",
help="Volume mounts for Docker container",
rich_help_panel="Docker Settings",
),
] = None,
docker_arg: Annotated[
list[str] | None,
typer.Option(
"--docker-arg",
help="Additional arguments to pass to docker run",
rich_help_panel="Docker Settings",
),
] = None,
docker_home: Annotated[
str | None,
typer.Option(
"--docker-home",
help="Override the home directory for Docker",
rich_help_panel="Docker Settings",
),
] = None,
docker_workspace: Annotated[
str | None,
typer.Option(
"--docker-workspace",
help="Override the workspace directory for Docker",
rich_help_panel="Docker Settings",
),
] = None,
user_mapping_enabled: Annotated[
bool | None,
typer.Option(
"--user-mapping/--no-user-mapping",
help="Enable user mapping for Docker",
rich_help_panel="Docker Settings",
),
] = None,
user_uid: Annotated[
int | None,
typer.Option(
"--user-uid",
help="User UID for Docker user mapping",
rich_help_panel="Docker Settings",
),
] = None,
user_gid: Annotated[
int | None,
typer.Option(
"--user-gid",
help="User GID for Docker user mapping",
rich_help_panel="Docker Settings",
),
] = None,
) -> None:
"""
Start the CCProxy API server.
This command starts the API server either locally or in Docker.
The server provides both Anthropic and OpenAI-compatible endpoints.
All configuration options can be provided via CLI parameters,
which override values from configuration files and environment variables.
Examples:
ccproxy serve
ccproxy serve --port 8080 --reload
ccproxy serve --docker
ccproxy serve --docker --docker-image custom:latest --port 8080
ccproxy serve --max-thinking-tokens 10000 --allowed-tools Read,Write,Bash
ccproxy serve --port 8080 --workers 4
"""
try:
# Early logging - use basic print until logging is configured
# We'll log this properly after logging is configured
# Get config path from context if not provided directly
if config is None:
config = get_config_path_from_context()
# Create option containers for better organization
server_options = ServerOptions(
port=port,
host=host,
reload=reload,
log_level=log_level,
log_file=log_file,
)
claude_options = ClaudeOptions(
max_thinking_tokens=max_thinking_tokens,
allowed_tools=allowed_tools,
disallowed_tools=disallowed_tools,
claude_cli_path=claude_cli_path,
append_system_prompt=append_system_prompt,
permission_mode=permission_mode,
max_turns=max_turns,
cwd=cwd,
permission_prompt_tool_name=permission_prompt_tool_name,
)
security_options = SecurityOptions(auth_token=auth_token)
# Extract CLI overrides from structured option containers
cli_overrides = config_manager.get_cli_overrides_from_args(
# Server options
host=server_options.host,
port=server_options.port,
reload=server_options.reload,
log_level=server_options.log_level,
log_file=server_options.log_file,
# Security options
auth_token=security_options.auth_token,
# Claude options
claude_cli_path=claude_options.claude_cli_path,
max_thinking_tokens=claude_options.max_thinking_tokens,
allowed_tools=claude_options.allowed_tools,
disallowed_tools=claude_options.disallowed_tools,
append_system_prompt=claude_options.append_system_prompt,
permission_mode=claude_options.permission_mode,
max_turns=claude_options.max_turns,
permission_prompt_tool_name=claude_options.permission_prompt_tool_name,
cwd=claude_options.cwd,
)
# Load settings with CLI overrides
settings = config_manager.load_settings(
config_path=config, cli_overrides=cli_overrides
)
# Set up logging once with the effective log level
# Import here to avoid circular import
import structlog
from ccproxy.core.logging import setup_logging
# Always reconfigure logging to ensure log level changes are picked up
# Use JSON logs if explicitly requested via env var
json_logs = os.environ.get("CCPROXY_JSON_LOGS", "").lower() == "true"
setup_logging(
json_logs=json_logs,
log_level=server_options.log_level or settings.server.log_level,
log_file=server_options.log_file or settings.server.log_file,
)
# Re-get logger after logging is configured
logger = get_logger(__name__)
# Test debug logging
logger.debug(
"Debug logging is enabled",
effective_log_level=server_options.log_level or settings.server.log_level,
)
# Log CLI command that was deferred
logger.info(
"cli_command_starting",
command="serve",
docker=docker,
port=server_options.port,
host=server_options.host,
config_path=str(config) if config else None,
)
# Log effective configuration
logger.info(
"configuration_loaded",
host=settings.server.host,
port=settings.server.port,
log_level=settings.server.log_level,
log_file=settings.server.log_file,
docker_mode=docker,
docker_image=settings.docker.docker_image if docker else None,
auth_enabled=bool(settings.security.auth_token),
duckdb_enabled=settings.observability.duckdb_enabled,
duckdb_path=settings.observability.duckdb_path
if settings.observability.duckdb_enabled
else None,
claude_cli_path=settings.claude.cli_path,
)
if docker:
_run_docker_server(
settings,
docker_image=docker_image,
docker_env=docker_env,
docker_volume=docker_volume,
docker_arg=docker_arg,
docker_home=docker_home,
docker_workspace=docker_workspace,
user_mapping_enabled=user_mapping_enabled,
user_uid=user_uid,
user_gid=user_gid,
)
else:
_run_local_server(settings, cli_overrides)
except ConfigurationError as e:
toolkit = get_rich_toolkit()
toolkit.print(f"Configuration error: {e}", tag="error")
raise typer.Exit(1) from e
except Exception as e:
toolkit = get_rich_toolkit()
toolkit.print(f"Error starting server: {e}", tag="error")
raise typer.Exit(1) from e