ccproxy-api 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. ccproxy/__init__.py +4 -0
  2. ccproxy/__main__.py +7 -0
  3. ccproxy/_version.py +21 -0
  4. ccproxy/adapters/__init__.py +11 -0
  5. ccproxy/adapters/base.py +80 -0
  6. ccproxy/adapters/openai/__init__.py +43 -0
  7. ccproxy/adapters/openai/adapter.py +915 -0
  8. ccproxy/adapters/openai/models.py +412 -0
  9. ccproxy/adapters/openai/streaming.py +449 -0
  10. ccproxy/api/__init__.py +28 -0
  11. ccproxy/api/app.py +225 -0
  12. ccproxy/api/dependencies.py +140 -0
  13. ccproxy/api/middleware/__init__.py +11 -0
  14. ccproxy/api/middleware/auth.py +0 -0
  15. ccproxy/api/middleware/cors.py +55 -0
  16. ccproxy/api/middleware/errors.py +703 -0
  17. ccproxy/api/middleware/headers.py +51 -0
  18. ccproxy/api/middleware/logging.py +175 -0
  19. ccproxy/api/middleware/request_id.py +69 -0
  20. ccproxy/api/middleware/server_header.py +62 -0
  21. ccproxy/api/responses.py +84 -0
  22. ccproxy/api/routes/__init__.py +16 -0
  23. ccproxy/api/routes/claude.py +181 -0
  24. ccproxy/api/routes/health.py +489 -0
  25. ccproxy/api/routes/metrics.py +1033 -0
  26. ccproxy/api/routes/proxy.py +238 -0
  27. ccproxy/auth/__init__.py +75 -0
  28. ccproxy/auth/bearer.py +68 -0
  29. ccproxy/auth/credentials_adapter.py +93 -0
  30. ccproxy/auth/dependencies.py +229 -0
  31. ccproxy/auth/exceptions.py +79 -0
  32. ccproxy/auth/manager.py +102 -0
  33. ccproxy/auth/models.py +118 -0
  34. ccproxy/auth/oauth/__init__.py +26 -0
  35. ccproxy/auth/oauth/models.py +49 -0
  36. ccproxy/auth/oauth/routes.py +396 -0
  37. ccproxy/auth/oauth/storage.py +0 -0
  38. ccproxy/auth/storage/__init__.py +12 -0
  39. ccproxy/auth/storage/base.py +57 -0
  40. ccproxy/auth/storage/json_file.py +159 -0
  41. ccproxy/auth/storage/keyring.py +192 -0
  42. ccproxy/claude_sdk/__init__.py +20 -0
  43. ccproxy/claude_sdk/client.py +169 -0
  44. ccproxy/claude_sdk/converter.py +331 -0
  45. ccproxy/claude_sdk/options.py +120 -0
  46. ccproxy/cli/__init__.py +14 -0
  47. ccproxy/cli/commands/__init__.py +8 -0
  48. ccproxy/cli/commands/auth.py +553 -0
  49. ccproxy/cli/commands/config/__init__.py +14 -0
  50. ccproxy/cli/commands/config/commands.py +766 -0
  51. ccproxy/cli/commands/config/schema_commands.py +119 -0
  52. ccproxy/cli/commands/serve.py +630 -0
  53. ccproxy/cli/docker/__init__.py +34 -0
  54. ccproxy/cli/docker/adapter_factory.py +157 -0
  55. ccproxy/cli/docker/params.py +278 -0
  56. ccproxy/cli/helpers.py +144 -0
  57. ccproxy/cli/main.py +193 -0
  58. ccproxy/cli/options/__init__.py +14 -0
  59. ccproxy/cli/options/claude_options.py +216 -0
  60. ccproxy/cli/options/core_options.py +40 -0
  61. ccproxy/cli/options/security_options.py +48 -0
  62. ccproxy/cli/options/server_options.py +117 -0
  63. ccproxy/config/__init__.py +40 -0
  64. ccproxy/config/auth.py +154 -0
  65. ccproxy/config/claude.py +124 -0
  66. ccproxy/config/cors.py +79 -0
  67. ccproxy/config/discovery.py +87 -0
  68. ccproxy/config/docker_settings.py +265 -0
  69. ccproxy/config/loader.py +108 -0
  70. ccproxy/config/observability.py +158 -0
  71. ccproxy/config/pricing.py +88 -0
  72. ccproxy/config/reverse_proxy.py +31 -0
  73. ccproxy/config/scheduler.py +89 -0
  74. ccproxy/config/security.py +14 -0
  75. ccproxy/config/server.py +81 -0
  76. ccproxy/config/settings.py +534 -0
  77. ccproxy/config/validators.py +231 -0
  78. ccproxy/core/__init__.py +274 -0
  79. ccproxy/core/async_utils.py +675 -0
  80. ccproxy/core/constants.py +97 -0
  81. ccproxy/core/errors.py +256 -0
  82. ccproxy/core/http.py +328 -0
  83. ccproxy/core/http_transformers.py +428 -0
  84. ccproxy/core/interfaces.py +247 -0
  85. ccproxy/core/logging.py +189 -0
  86. ccproxy/core/middleware.py +114 -0
  87. ccproxy/core/proxy.py +143 -0
  88. ccproxy/core/system.py +38 -0
  89. ccproxy/core/transformers.py +259 -0
  90. ccproxy/core/types.py +129 -0
  91. ccproxy/core/validators.py +288 -0
  92. ccproxy/docker/__init__.py +67 -0
  93. ccproxy/docker/adapter.py +588 -0
  94. ccproxy/docker/docker_path.py +207 -0
  95. ccproxy/docker/middleware.py +103 -0
  96. ccproxy/docker/models.py +228 -0
  97. ccproxy/docker/protocol.py +192 -0
  98. ccproxy/docker/stream_process.py +264 -0
  99. ccproxy/docker/validators.py +173 -0
  100. ccproxy/models/__init__.py +123 -0
  101. ccproxy/models/errors.py +42 -0
  102. ccproxy/models/messages.py +243 -0
  103. ccproxy/models/requests.py +85 -0
  104. ccproxy/models/responses.py +227 -0
  105. ccproxy/models/types.py +102 -0
  106. ccproxy/observability/__init__.py +51 -0
  107. ccproxy/observability/access_logger.py +400 -0
  108. ccproxy/observability/context.py +447 -0
  109. ccproxy/observability/metrics.py +539 -0
  110. ccproxy/observability/pushgateway.py +366 -0
  111. ccproxy/observability/sse_events.py +303 -0
  112. ccproxy/observability/stats_printer.py +755 -0
  113. ccproxy/observability/storage/__init__.py +1 -0
  114. ccproxy/observability/storage/duckdb_simple.py +665 -0
  115. ccproxy/observability/storage/models.py +55 -0
  116. ccproxy/pricing/__init__.py +19 -0
  117. ccproxy/pricing/cache.py +212 -0
  118. ccproxy/pricing/loader.py +267 -0
  119. ccproxy/pricing/models.py +106 -0
  120. ccproxy/pricing/updater.py +309 -0
  121. ccproxy/scheduler/__init__.py +39 -0
  122. ccproxy/scheduler/core.py +335 -0
  123. ccproxy/scheduler/exceptions.py +34 -0
  124. ccproxy/scheduler/manager.py +186 -0
  125. ccproxy/scheduler/registry.py +150 -0
  126. ccproxy/scheduler/tasks.py +484 -0
  127. ccproxy/services/__init__.py +10 -0
  128. ccproxy/services/claude_sdk_service.py +614 -0
  129. ccproxy/services/credentials/__init__.py +55 -0
  130. ccproxy/services/credentials/config.py +105 -0
  131. ccproxy/services/credentials/manager.py +562 -0
  132. ccproxy/services/credentials/oauth_client.py +482 -0
  133. ccproxy/services/proxy_service.py +1536 -0
  134. ccproxy/static/.keep +0 -0
  135. ccproxy/testing/__init__.py +34 -0
  136. ccproxy/testing/config.py +148 -0
  137. ccproxy/testing/content_generation.py +197 -0
  138. ccproxy/testing/mock_responses.py +262 -0
  139. ccproxy/testing/response_handlers.py +161 -0
  140. ccproxy/testing/scenarios.py +241 -0
  141. ccproxy/utils/__init__.py +6 -0
  142. ccproxy/utils/cost_calculator.py +210 -0
  143. ccproxy/utils/streaming_metrics.py +199 -0
  144. ccproxy_api-0.1.0.dist-info/METADATA +253 -0
  145. ccproxy_api-0.1.0.dist-info/RECORD +148 -0
  146. ccproxy_api-0.1.0.dist-info/WHEEL +4 -0
  147. ccproxy_api-0.1.0.dist-info/entry_points.txt +2 -0
  148. ccproxy_api-0.1.0.dist-info/licenses/LICENSE +21 -0
ccproxy/cli/main.py ADDED
@@ -0,0 +1,193 @@
1
+ """Main entry point for CCProxy API Server."""
2
+
3
+ import json
4
+ import os
5
+ import secrets
6
+ from pathlib import Path
7
+ from typing import Any, Optional, cast
8
+
9
+ import typer
10
+ from click import get_current_context
11
+ from structlog import get_logger
12
+ from typer import Typer
13
+
14
+ from ccproxy._version import __version__
15
+ from ccproxy.api.middleware.cors import setup_cors_middleware
16
+ from ccproxy.cli.helpers import (
17
+ get_rich_toolkit,
18
+ is_running_in_docker,
19
+ warning,
20
+ )
21
+ from ccproxy.config.settings import (
22
+ ConfigurationError,
23
+ Settings,
24
+ config_manager,
25
+ get_settings,
26
+ )
27
+ from ccproxy.core.async_utils import get_package_dir, get_root_package_name
28
+ from ccproxy.core.logging import setup_logging
29
+ from ccproxy.models.responses import (
30
+ PermissionToolAllowResponse,
31
+ PermissionToolDenyResponse,
32
+ )
33
+
34
+ from .commands.auth import app as auth_app
35
+ from .commands.config import app as config_app
36
+ from .commands.serve import api, get_config_path_from_context
37
+
38
+
39
+ def version_callback(value: bool) -> None:
40
+ """Print version and exit."""
41
+ if value:
42
+ toolkit = get_rich_toolkit()
43
+ toolkit.print(f"ccproxy {__version__}", tag="version")
44
+ raise typer.Exit()
45
+
46
+
47
+ app = typer.Typer(
48
+ rich_markup_mode="rich",
49
+ add_completion=True,
50
+ no_args_is_help=False,
51
+ pretty_exceptions_enable=False,
52
+ )
53
+
54
+ # Logger will be configured by configuration manager
55
+ logger = get_logger(__name__)
56
+
57
+
58
+ # Add global options
59
+ @app.callback()
60
+ def app_main(
61
+ ctx: typer.Context,
62
+ version: bool = typer.Option(
63
+ False,
64
+ "--version",
65
+ "-V",
66
+ callback=version_callback,
67
+ is_eager=True,
68
+ help="Show version and exit.",
69
+ ),
70
+ config: Path | None = typer.Option(
71
+ None,
72
+ "--config",
73
+ "-c",
74
+ help="Path to configuration file (TOML, JSON, or YAML)",
75
+ exists=True,
76
+ file_okay=True,
77
+ dir_okay=False,
78
+ readable=True,
79
+ ),
80
+ ) -> None:
81
+ """CCProxy API Server - Anthropic and OpenAI compatible interface for Claude."""
82
+ # Store config path for commands to use
83
+ ctx.ensure_object(dict)
84
+ ctx.obj["config_path"] = config
85
+
86
+
87
+ # Register config command
88
+ app.add_typer(config_app)
89
+
90
+ # Register auth command
91
+ app.add_typer(auth_app)
92
+
93
+
94
+ # Register imported commands
95
+ app.command(name="serve")(api)
96
+ # Claude command removed - functionality moved to serve command
97
+
98
+
99
+ @app.command()
100
+ def permission_tool(
101
+ tool_name: str = typer.Argument(
102
+ ..., help="Name of the tool to check permissions for"
103
+ ),
104
+ tool_input: str = typer.Argument(..., help="JSON string of the tool input"),
105
+ ) -> None:
106
+ """
107
+ MCP permission prompt tool for Claude Code SDK.
108
+
109
+ This tool is used by the Claude Code SDK to check permissions for tool calls.
110
+ It returns a JSON response indicating whether the tool call should be allowed or denied.
111
+
112
+ Response format:
113
+ - Allow: {"behavior": "allow", "updatedInput": {...}}
114
+ - Deny: {"behavior": "deny", "message": "reason"}
115
+
116
+ Examples:
117
+ ccproxy permission_tool "bash" '{"command": "ls -la"}'
118
+ ccproxy permission_tool "edit_file" '{"path": "/etc/passwd", "content": "..."}'
119
+ """
120
+ toolkit = get_rich_toolkit()
121
+
122
+ try:
123
+ # Parse the tool input JSON
124
+ try:
125
+ input_data = json.loads(tool_input)
126
+ except json.JSONDecodeError as e:
127
+ response = PermissionToolDenyResponse(message=f"Invalid JSON input: {e}")
128
+ toolkit.print(response.model_dump_json(by_alias=True), tag="result")
129
+ raise typer.Exit(1) from e
130
+
131
+ # Load settings to get permission configuration
132
+ settings = config_manager.load_settings(
133
+ config_path=get_config_path_from_context()
134
+ )
135
+
136
+ # Basic permission checking logic
137
+ # This can be extended with more sophisticated rules
138
+
139
+ # Check for potentially dangerous commands
140
+ dangerous_patterns = [
141
+ "rm -rf",
142
+ "sudo",
143
+ "passwd",
144
+ "chmod 777",
145
+ "/etc/passwd",
146
+ "/etc/shadow",
147
+ "format",
148
+ "mkfs",
149
+ ]
150
+
151
+ # Convert input to string for pattern matching
152
+ input_str = json.dumps(input_data).lower()
153
+
154
+ # Check for dangerous patterns
155
+ for pattern in dangerous_patterns:
156
+ if pattern in input_str:
157
+ response = PermissionToolDenyResponse(
158
+ message=f"Tool call contains potentially dangerous pattern: {pattern}"
159
+ )
160
+ toolkit.print(response.model_dump_json(by_alias=True), tag="result")
161
+ return
162
+
163
+ # Check for specific tool restrictions
164
+ restricted_tools = {"exec", "system", "shell", "subprocess"}
165
+
166
+ if tool_name.lower() in restricted_tools:
167
+ response = PermissionToolDenyResponse(
168
+ message=f"Tool {tool_name} is restricted for security reasons"
169
+ )
170
+ toolkit.print(response.model_dump_json(by_alias=True), tag="result")
171
+ return
172
+
173
+ # Allow the tool call with original input
174
+ allow_response = PermissionToolAllowResponse(updated_input=input_data)
175
+ toolkit.print(allow_response.model_dump_json(by_alias=True), tag="result")
176
+
177
+ except Exception as e:
178
+ error_response = PermissionToolDenyResponse(
179
+ message=f"Error processing permission request: {e}"
180
+ )
181
+ toolkit.print(error_response.model_dump_json(by_alias=True), tag="result")
182
+ raise typer.Exit(1) from e
183
+
184
+
185
+ def main() -> None:
186
+ """Entry point for the CLI application."""
187
+ app()
188
+
189
+
190
+ if __name__ == "__main__":
191
+ import sys
192
+
193
+ sys.exit(app())
@@ -0,0 +1,14 @@
1
+ """CLI option modules for organized command-line argument handling."""
2
+
3
+ from .claude_options import ClaudeOptions
4
+ from .core_options import CoreOptions
5
+ from .security_options import SecurityOptions
6
+ from .server_options import ServerOptions
7
+
8
+
9
+ __all__ = [
10
+ "CoreOptions",
11
+ "ServerOptions",
12
+ "ClaudeOptions",
13
+ "SecurityOptions",
14
+ ]
@@ -0,0 +1,216 @@
1
+ """Claude-specific CLI options."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import typer
7
+
8
+
9
+ def validate_max_thinking_tokens(
10
+ ctx: typer.Context, param: typer.CallbackParam, value: int | None
11
+ ) -> int | None:
12
+ """Validate max thinking tokens."""
13
+ if value is None:
14
+ return None
15
+
16
+ if value < 0:
17
+ raise typer.BadParameter("Max thinking tokens must be non-negative")
18
+
19
+ return value
20
+
21
+
22
+ def validate_max_turns(
23
+ ctx: typer.Context, param: typer.CallbackParam, value: int | None
24
+ ) -> int | None:
25
+ """Validate max turns."""
26
+ if value is None:
27
+ return None
28
+
29
+ if value < 1:
30
+ raise typer.BadParameter("Max turns must be at least 1")
31
+
32
+ return value
33
+
34
+
35
+ def validate_permission_mode(
36
+ ctx: typer.Context, param: typer.CallbackParam, value: str | None
37
+ ) -> str | None:
38
+ """Validate permission mode."""
39
+ if value is None:
40
+ return None
41
+
42
+ valid_modes = {"default", "acceptEdits", "bypassPermissions"}
43
+ if value not in valid_modes:
44
+ raise typer.BadParameter(
45
+ f"Permission mode must be one of: {', '.join(valid_modes)}"
46
+ )
47
+
48
+ return value
49
+
50
+
51
+ def validate_claude_cli_path(
52
+ ctx: typer.Context, param: typer.CallbackParam, value: str | None
53
+ ) -> str | None:
54
+ """Validate Claude CLI path."""
55
+ if value is None:
56
+ return None
57
+
58
+ path = Path(value)
59
+ if not path.exists():
60
+ raise typer.BadParameter(f"Claude CLI path does not exist: {value}")
61
+
62
+ return value
63
+
64
+
65
+ def validate_cwd(
66
+ ctx: typer.Context, param: typer.CallbackParam, value: str | None
67
+ ) -> str | None:
68
+ """Validate working directory."""
69
+ if value is None:
70
+ return None
71
+
72
+ path = Path(value)
73
+ if not path.exists():
74
+ raise typer.BadParameter(f"Working directory does not exist: {value}")
75
+ if not path.is_dir():
76
+ raise typer.BadParameter(f"Working directory is not a directory: {value}")
77
+
78
+ return value
79
+
80
+
81
+ def max_thinking_tokens_option() -> Any:
82
+ """Max thinking tokens parameter."""
83
+ return typer.Option(
84
+ None,
85
+ "--max-thinking-tokens",
86
+ help="Maximum thinking tokens for Claude Code",
87
+ callback=validate_max_thinking_tokens,
88
+ rich_help_panel="Claude Settings",
89
+ )
90
+
91
+
92
+ def allowed_tools_option() -> Any:
93
+ """Allowed tools parameter."""
94
+ return typer.Option(
95
+ None,
96
+ "--allowed-tools",
97
+ help="List of allowed tools (comma-separated)",
98
+ rich_help_panel="Claude Settings",
99
+ )
100
+
101
+
102
+ def disallowed_tools_option() -> Any:
103
+ """Disallowed tools parameter."""
104
+ return typer.Option(
105
+ None,
106
+ "--disallowed-tools",
107
+ help="List of disallowed tools (comma-separated)",
108
+ rich_help_panel="Claude Settings",
109
+ )
110
+
111
+
112
+ def claude_cli_path_option() -> Any:
113
+ """Claude CLI path parameter."""
114
+ return typer.Option(
115
+ None,
116
+ "--claude-cli-path",
117
+ help="Path to Claude CLI executable",
118
+ callback=validate_claude_cli_path,
119
+ rich_help_panel="Claude Settings",
120
+ )
121
+
122
+
123
+ def append_system_prompt_option() -> Any:
124
+ """Append system prompt parameter."""
125
+ return typer.Option(
126
+ None,
127
+ "--append-system-prompt",
128
+ help="Additional system prompt to append",
129
+ rich_help_panel="Claude Settings",
130
+ )
131
+
132
+
133
+ def permission_mode_option() -> Any:
134
+ """Permission mode parameter."""
135
+ return typer.Option(
136
+ None,
137
+ "--permission-mode",
138
+ help="Permission mode: default, acceptEdits, or bypassPermissions",
139
+ callback=validate_permission_mode,
140
+ rich_help_panel="Claude Settings",
141
+ )
142
+
143
+
144
+ def max_turns_option() -> Any:
145
+ """Max turns parameter."""
146
+ return typer.Option(
147
+ None,
148
+ "--max-turns",
149
+ help="Maximum conversation turns",
150
+ callback=validate_max_turns,
151
+ rich_help_panel="Claude Settings",
152
+ )
153
+
154
+
155
+ def cwd_option() -> Any:
156
+ """Working directory parameter."""
157
+ return typer.Option(
158
+ None,
159
+ "--cwd",
160
+ help="Working directory path",
161
+ callback=validate_cwd,
162
+ rich_help_panel="Claude Settings",
163
+ )
164
+
165
+
166
+ def permission_prompt_tool_name_option() -> Any:
167
+ """Permission prompt tool name parameter."""
168
+ return typer.Option(
169
+ None,
170
+ "--permission-prompt-tool-name",
171
+ help="Permission prompt tool name",
172
+ rich_help_panel="Claude Settings",
173
+ )
174
+
175
+
176
+ class ClaudeOptions:
177
+ """Container for all Claude-related CLI options.
178
+
179
+ This class provides a convenient way to include all Claude-related
180
+ options in a command using typed attributes.
181
+ """
182
+
183
+ def __init__(
184
+ self,
185
+ max_thinking_tokens: int | None = None,
186
+ allowed_tools: str | None = None,
187
+ disallowed_tools: str | None = None,
188
+ claude_cli_path: str | None = None,
189
+ append_system_prompt: str | None = None,
190
+ permission_mode: str | None = None,
191
+ max_turns: int | None = None,
192
+ cwd: str | None = None,
193
+ permission_prompt_tool_name: str | None = None,
194
+ ):
195
+ """Initialize Claude options.
196
+
197
+ Args:
198
+ max_thinking_tokens: Maximum thinking tokens for Claude Code
199
+ allowed_tools: List of allowed tools (comma-separated)
200
+ disallowed_tools: List of disallowed tools (comma-separated)
201
+ claude_cli_path: Path to Claude CLI executable
202
+ append_system_prompt: Additional system prompt to append
203
+ permission_mode: Permission mode
204
+ max_turns: Maximum conversation turns
205
+ cwd: Working directory path
206
+ permission_prompt_tool_name: Permission prompt tool name
207
+ """
208
+ self.max_thinking_tokens = max_thinking_tokens
209
+ self.allowed_tools = allowed_tools
210
+ self.disallowed_tools = disallowed_tools
211
+ self.claude_cli_path = claude_cli_path
212
+ self.append_system_prompt = append_system_prompt
213
+ self.permission_mode = permission_mode
214
+ self.max_turns = max_turns
215
+ self.cwd = cwd
216
+ self.permission_prompt_tool_name = permission_prompt_tool_name
@@ -0,0 +1,40 @@
1
+ """Core CLI options for configuration and global settings."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import typer
7
+
8
+
9
+ def config_option() -> Any:
10
+ """Configuration file parameter."""
11
+ return typer.Option(
12
+ None,
13
+ "--config",
14
+ "-c",
15
+ help="Path to configuration file (TOML, JSON, or YAML)",
16
+ exists=True,
17
+ file_okay=True,
18
+ dir_okay=False,
19
+ readable=True,
20
+ rich_help_panel="Configuration",
21
+ )
22
+
23
+
24
+ class CoreOptions:
25
+ """Container for core CLI options.
26
+
27
+ This class provides a convenient way to include core options
28
+ in a command using typed attributes.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ config: Path | None = None,
34
+ ):
35
+ """Initialize core options.
36
+
37
+ Args:
38
+ config: Path to configuration file
39
+ """
40
+ self.config = config
@@ -0,0 +1,48 @@
1
+ """Security-related CLI options."""
2
+
3
+ from typing import Any
4
+
5
+ import typer
6
+
7
+
8
+ def validate_auth_token(
9
+ ctx: typer.Context, param: typer.CallbackParam, value: str | None
10
+ ) -> str | None:
11
+ """Validate auth token."""
12
+ if value is None:
13
+ return None
14
+
15
+ if not value.strip():
16
+ raise typer.BadParameter("Auth token cannot be empty")
17
+
18
+ return value
19
+
20
+
21
+ def auth_token_option() -> Any:
22
+ """Auth token parameter."""
23
+ return typer.Option(
24
+ None,
25
+ "--auth-token",
26
+ help="Bearer token for API authentication",
27
+ callback=validate_auth_token,
28
+ rich_help_panel="Security Settings",
29
+ )
30
+
31
+
32
+ class SecurityOptions:
33
+ """Container for all security-related CLI options.
34
+
35
+ This class provides a convenient way to include all security-related
36
+ options in a command using typed attributes.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ auth_token: str | None = None,
42
+ ):
43
+ """Initialize security options.
44
+
45
+ Args:
46
+ auth_token: Bearer token for API authentication
47
+ """
48
+ self.auth_token = auth_token
@@ -0,0 +1,117 @@
1
+ """Server-related CLI options."""
2
+
3
+ from typing import Any
4
+
5
+ import typer
6
+
7
+
8
+ def validate_port(
9
+ ctx: typer.Context, param: typer.CallbackParam, value: int | None
10
+ ) -> int | None:
11
+ """Validate port number."""
12
+ if value is None:
13
+ return None
14
+
15
+ if value < 1 or value > 65535:
16
+ raise typer.BadParameter("Port must be between 1 and 65535")
17
+
18
+ return value
19
+
20
+
21
+ def validate_log_level(
22
+ ctx: typer.Context, param: typer.CallbackParam, value: str | None
23
+ ) -> str | None:
24
+ """Validate log level."""
25
+ if value is None:
26
+ return None
27
+
28
+ valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
29
+ if value.upper() not in valid_levels:
30
+ raise typer.BadParameter(f"Log level must be one of: {', '.join(valid_levels)}")
31
+
32
+ return value.upper()
33
+
34
+
35
+ def port_option() -> Any:
36
+ """Port parameter."""
37
+ return typer.Option(
38
+ None,
39
+ "--port",
40
+ "-p",
41
+ help="Port to run the server on",
42
+ callback=validate_port,
43
+ rich_help_panel="Server Settings",
44
+ )
45
+
46
+
47
+ def host_option() -> Any:
48
+ """Host parameter."""
49
+ return typer.Option(
50
+ None,
51
+ "--host",
52
+ "-h",
53
+ help="Host to bind the server to",
54
+ rich_help_panel="Server Settings",
55
+ )
56
+
57
+
58
+ def reload_option() -> Any:
59
+ """Reload parameter."""
60
+ return typer.Option(
61
+ None,
62
+ "--reload/--no-reload",
63
+ help="Enable auto-reload for development",
64
+ rich_help_panel="Server Settings",
65
+ )
66
+
67
+
68
+ def log_level_option() -> Any:
69
+ """Log level parameter."""
70
+ return typer.Option(
71
+ None,
72
+ "--log-level",
73
+ help="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
74
+ callback=validate_log_level,
75
+ rich_help_panel="Server Settings",
76
+ )
77
+
78
+
79
+ def log_file_option() -> Any:
80
+ """Log file parameter."""
81
+ return typer.Option(
82
+ None,
83
+ "--log-file",
84
+ help="Path to JSON log file. If specified, logs will be written to this file in JSON format",
85
+ rich_help_panel="Server Settings",
86
+ )
87
+
88
+
89
+ class ServerOptions:
90
+ """Container for all server-related CLI options.
91
+
92
+ This class provides a convenient way to include all server-related
93
+ options in a command using typed attributes.
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ port: int | None = None,
99
+ host: str | None = None,
100
+ reload: bool | None = None,
101
+ log_level: str | None = None,
102
+ log_file: str | None = None,
103
+ ):
104
+ """Initialize server options.
105
+
106
+ Args:
107
+ port: Port to run the server on
108
+ host: Host to bind the server to
109
+ reload: Enable auto-reload for development
110
+ log_level: Logging level
111
+ log_file: Path to JSON log file
112
+ """
113
+ self.port = port
114
+ self.host = host
115
+ self.reload = reload
116
+ self.log_level = log_level
117
+ self.log_file = log_file
@@ -0,0 +1,40 @@
1
+ """Configuration module for Claude Proxy API Server."""
2
+
3
+ from .auth import AuthSettings, CredentialStorageSettings, OAuthSettings
4
+ from .docker_settings import DockerSettings
5
+ from .loader import ConfigLoader, load_config
6
+ from .reverse_proxy import ReverseProxySettings
7
+ from .settings import Settings, get_settings
8
+ from .validators import (
9
+ ConfigValidationError,
10
+ validate_config_dict,
11
+ validate_cors_origins,
12
+ validate_host,
13
+ validate_log_level,
14
+ validate_path,
15
+ validate_port,
16
+ validate_timeout,
17
+ validate_url,
18
+ )
19
+
20
+
21
+ __all__ = [
22
+ "Settings",
23
+ "get_settings",
24
+ "AuthSettings",
25
+ "OAuthSettings",
26
+ "CredentialStorageSettings",
27
+ "ReverseProxySettings",
28
+ "DockerSettings",
29
+ "ConfigLoader",
30
+ "load_config",
31
+ "ConfigValidationError",
32
+ "validate_config_dict",
33
+ "validate_cors_origins",
34
+ "validate_host",
35
+ "validate_log_level",
36
+ "validate_path",
37
+ "validate_port",
38
+ "validate_timeout",
39
+ "validate_url",
40
+ ]