ccproxy-api 0.1.0__py3-none-any.whl → 0.1.2__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.
- ccproxy/_version.py +2 -2
- ccproxy/api/app.py +70 -0
- ccproxy/api/dependencies.py +3 -0
- ccproxy/api/routes/claude.py +0 -6
- ccproxy/api/routes/proxy.py +13 -6
- ccproxy/auth/conditional.py +84 -0
- ccproxy/claude_sdk/converter.py +1 -1
- ccproxy/claude_sdk/options.py +23 -1
- ccproxy/cli/commands/__init__.py +2 -1
- ccproxy/cli/commands/auth.py +59 -43
- ccproxy/cli/commands/config/commands.py +2 -2
- ccproxy/cli/commands/permission.py +128 -0
- ccproxy/cli/commands/serve.py +322 -82
- ccproxy/cli/main.py +33 -111
- ccproxy/cli/options/claude_options.py +1 -93
- ccproxy/cli/options/core_options.py +1 -13
- ccproxy/cli/options/security_options.py +1 -9
- ccproxy/cli/options/server_options.py +1 -52
- ccproxy/config/auth.py +2 -2
- ccproxy/config/docker_settings.py +1 -1
- ccproxy/config/pricing.py +5 -6
- ccproxy/config/scheduler.py +5 -6
- ccproxy/core/async_utils.py +1 -1
- ccproxy/core/types.py +3 -9
- ccproxy/models/messages.py +1 -2
- ccproxy/pricing/models.py +5 -6
- ccproxy/services/claude_sdk_service.py +5 -1
- ccproxy/services/proxy_service.py +6 -7
- {ccproxy_api-0.1.0.dist-info → ccproxy_api-0.1.2.dist-info}/METADATA +62 -7
- {ccproxy_api-0.1.0.dist-info → ccproxy_api-0.1.2.dist-info}/RECORD +33 -31
- ccproxy_api-0.1.2.dist-info/entry_points.txt +4 -0
- ccproxy_api-0.1.0.dist-info/entry_points.txt +0 -2
- {ccproxy_api-0.1.0.dist-info → ccproxy_api-0.1.2.dist-info}/WHEEL +0 -0
- {ccproxy_api-0.1.0.dist-info → ccproxy_api-0.1.2.dist-info}/licenses/LICENSE +0 -0
ccproxy/cli/commands/serve.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Annotated, Any
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
import uvicorn
|
|
@@ -23,48 +23,25 @@ from ccproxy.config.settings import (
|
|
|
23
23
|
)
|
|
24
24
|
from ccproxy.core.async_utils import get_root_package_name
|
|
25
25
|
from ccproxy.docker import (
|
|
26
|
-
DockerEnv,
|
|
27
|
-
DockerPath,
|
|
28
|
-
DockerUserContext,
|
|
29
|
-
DockerVolume,
|
|
30
26
|
create_docker_adapter,
|
|
31
27
|
)
|
|
32
28
|
|
|
33
29
|
from ..docker import (
|
|
34
30
|
_create_docker_adapter_from_settings,
|
|
35
31
|
)
|
|
36
|
-
from ..docker.params import (
|
|
37
|
-
docker_arg_option,
|
|
38
|
-
docker_env_option,
|
|
39
|
-
docker_home_option,
|
|
40
|
-
docker_image_option,
|
|
41
|
-
docker_volume_option,
|
|
42
|
-
docker_workspace_option,
|
|
43
|
-
user_gid_option,
|
|
44
|
-
user_mapping_option,
|
|
45
|
-
user_uid_option,
|
|
46
|
-
)
|
|
47
32
|
from ..options.claude_options import (
|
|
48
33
|
ClaudeOptions,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
max_thinking_tokens_option,
|
|
55
|
-
max_turns_option,
|
|
56
|
-
permission_mode_option,
|
|
57
|
-
permission_prompt_tool_name_option,
|
|
34
|
+
validate_claude_cli_path,
|
|
35
|
+
validate_cwd,
|
|
36
|
+
validate_max_thinking_tokens,
|
|
37
|
+
validate_max_turns,
|
|
38
|
+
validate_permission_mode,
|
|
58
39
|
)
|
|
59
|
-
from ..options.
|
|
60
|
-
from ..options.security_options import SecurityOptions, auth_token_option
|
|
40
|
+
from ..options.security_options import SecurityOptions, validate_auth_token
|
|
61
41
|
from ..options.server_options import (
|
|
62
42
|
ServerOptions,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
log_level_option,
|
|
66
|
-
port_option,
|
|
67
|
-
reload_option,
|
|
43
|
+
validate_log_level,
|
|
44
|
+
validate_port,
|
|
68
45
|
)
|
|
69
46
|
|
|
70
47
|
|
|
@@ -306,42 +283,236 @@ def _run_local_server(settings: Settings, cli_overrides: dict[str, Any]) -> None
|
|
|
306
283
|
|
|
307
284
|
def api(
|
|
308
285
|
# Configuration
|
|
309
|
-
config:
|
|
286
|
+
config: Annotated[
|
|
287
|
+
Path | None,
|
|
288
|
+
typer.Option(
|
|
289
|
+
"--config",
|
|
290
|
+
"-c",
|
|
291
|
+
help="Path to configuration file (TOML, JSON, or YAML)",
|
|
292
|
+
exists=True,
|
|
293
|
+
file_okay=True,
|
|
294
|
+
dir_okay=False,
|
|
295
|
+
readable=True,
|
|
296
|
+
rich_help_panel="Configuration",
|
|
297
|
+
),
|
|
298
|
+
] = None,
|
|
310
299
|
# Server options
|
|
311
|
-
port:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
300
|
+
port: Annotated[
|
|
301
|
+
int | None,
|
|
302
|
+
typer.Option(
|
|
303
|
+
"--port",
|
|
304
|
+
"-p",
|
|
305
|
+
help="Port to run the server on",
|
|
306
|
+
callback=validate_port,
|
|
307
|
+
rich_help_panel="Server Settings",
|
|
308
|
+
),
|
|
309
|
+
] = None,
|
|
310
|
+
host: Annotated[
|
|
311
|
+
str | None,
|
|
312
|
+
typer.Option(
|
|
313
|
+
"--host",
|
|
314
|
+
"-h",
|
|
315
|
+
help="Host to bind the server to",
|
|
316
|
+
rich_help_panel="Server Settings",
|
|
317
|
+
),
|
|
318
|
+
] = None,
|
|
319
|
+
reload: Annotated[
|
|
320
|
+
bool | None,
|
|
321
|
+
typer.Option(
|
|
322
|
+
"--reload/--no-reload",
|
|
323
|
+
help="Enable auto-reload for development",
|
|
324
|
+
rich_help_panel="Server Settings",
|
|
325
|
+
),
|
|
326
|
+
] = None,
|
|
327
|
+
log_level: Annotated[
|
|
328
|
+
str | None,
|
|
329
|
+
typer.Option(
|
|
330
|
+
"--log-level",
|
|
331
|
+
help="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
332
|
+
callback=validate_log_level,
|
|
333
|
+
rich_help_panel="Server Settings",
|
|
334
|
+
),
|
|
335
|
+
] = None,
|
|
336
|
+
log_file: Annotated[
|
|
337
|
+
str | None,
|
|
338
|
+
typer.Option(
|
|
339
|
+
"--log-file",
|
|
340
|
+
help="Path to JSON log file. If specified, logs will be written to this file in JSON format",
|
|
341
|
+
rich_help_panel="Server Settings",
|
|
342
|
+
),
|
|
343
|
+
] = None,
|
|
316
344
|
# Security options
|
|
317
|
-
auth_token:
|
|
345
|
+
auth_token: Annotated[
|
|
346
|
+
str | None,
|
|
347
|
+
typer.Option(
|
|
348
|
+
"--auth-token",
|
|
349
|
+
help="Bearer token for API authentication",
|
|
350
|
+
callback=validate_auth_token,
|
|
351
|
+
rich_help_panel="Security Settings",
|
|
352
|
+
),
|
|
353
|
+
] = None,
|
|
318
354
|
# Claude options
|
|
319
|
-
max_thinking_tokens:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
355
|
+
max_thinking_tokens: Annotated[
|
|
356
|
+
int | None,
|
|
357
|
+
typer.Option(
|
|
358
|
+
"--max-thinking-tokens",
|
|
359
|
+
help="Maximum thinking tokens for Claude Code",
|
|
360
|
+
callback=validate_max_thinking_tokens,
|
|
361
|
+
rich_help_panel="Claude Settings",
|
|
362
|
+
),
|
|
363
|
+
] = None,
|
|
364
|
+
allowed_tools: Annotated[
|
|
365
|
+
str | None,
|
|
366
|
+
typer.Option(
|
|
367
|
+
"--allowed-tools",
|
|
368
|
+
help="List of allowed tools (comma-separated)",
|
|
369
|
+
rich_help_panel="Claude Settings",
|
|
370
|
+
),
|
|
371
|
+
] = None,
|
|
372
|
+
disallowed_tools: Annotated[
|
|
373
|
+
str | None,
|
|
374
|
+
typer.Option(
|
|
375
|
+
"--disallowed-tools",
|
|
376
|
+
help="List of disallowed tools (comma-separated)",
|
|
377
|
+
rich_help_panel="Claude Settings",
|
|
378
|
+
),
|
|
379
|
+
] = None,
|
|
380
|
+
claude_cli_path: Annotated[
|
|
381
|
+
str | None,
|
|
382
|
+
typer.Option(
|
|
383
|
+
"--claude-cli-path",
|
|
384
|
+
help="Path to Claude CLI executable",
|
|
385
|
+
callback=validate_claude_cli_path,
|
|
386
|
+
rich_help_panel="Claude Settings",
|
|
387
|
+
),
|
|
388
|
+
] = None,
|
|
389
|
+
append_system_prompt: Annotated[
|
|
390
|
+
str | None,
|
|
391
|
+
typer.Option(
|
|
392
|
+
"--append-system-prompt",
|
|
393
|
+
help="Additional system prompt to append",
|
|
394
|
+
rich_help_panel="Claude Settings",
|
|
395
|
+
),
|
|
396
|
+
] = None,
|
|
397
|
+
permission_mode: Annotated[
|
|
398
|
+
str | None,
|
|
399
|
+
typer.Option(
|
|
400
|
+
"--permission-mode",
|
|
401
|
+
help="Permission mode: default, acceptEdits, or bypassPermissions",
|
|
402
|
+
callback=validate_permission_mode,
|
|
403
|
+
rich_help_panel="Claude Settings",
|
|
404
|
+
),
|
|
405
|
+
] = None,
|
|
406
|
+
max_turns: Annotated[
|
|
407
|
+
int | None,
|
|
408
|
+
typer.Option(
|
|
409
|
+
"--max-turns",
|
|
410
|
+
help="Maximum conversation turns",
|
|
411
|
+
callback=validate_max_turns,
|
|
412
|
+
rich_help_panel="Claude Settings",
|
|
413
|
+
),
|
|
414
|
+
] = None,
|
|
415
|
+
cwd: Annotated[
|
|
416
|
+
str | None,
|
|
417
|
+
typer.Option(
|
|
418
|
+
"--cwd",
|
|
419
|
+
help="Working directory path",
|
|
420
|
+
callback=validate_cwd,
|
|
421
|
+
rich_help_panel="Claude Settings",
|
|
422
|
+
),
|
|
423
|
+
] = None,
|
|
424
|
+
permission_prompt_tool_name: Annotated[
|
|
425
|
+
str | None,
|
|
426
|
+
typer.Option(
|
|
427
|
+
"--permission-prompt-tool-name",
|
|
428
|
+
help="Permission prompt tool name",
|
|
429
|
+
rich_help_panel="Claude Settings",
|
|
430
|
+
),
|
|
431
|
+
] = None,
|
|
328
432
|
# Core settings
|
|
329
|
-
docker:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
433
|
+
docker: Annotated[
|
|
434
|
+
bool,
|
|
435
|
+
typer.Option(
|
|
436
|
+
"--docker",
|
|
437
|
+
"-d",
|
|
438
|
+
help="Run API server using Docker instead of local execution",
|
|
439
|
+
),
|
|
440
|
+
] = False,
|
|
335
441
|
# Docker settings using shared parameters
|
|
336
|
-
docker_image:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
442
|
+
docker_image: Annotated[
|
|
443
|
+
str | None,
|
|
444
|
+
typer.Option(
|
|
445
|
+
"--docker-image",
|
|
446
|
+
help="Docker image to use (overrides configuration)",
|
|
447
|
+
rich_help_panel="Docker Settings",
|
|
448
|
+
),
|
|
449
|
+
] = None,
|
|
450
|
+
docker_env: Annotated[
|
|
451
|
+
list[str] | None,
|
|
452
|
+
typer.Option(
|
|
453
|
+
"--docker-env",
|
|
454
|
+
"-e",
|
|
455
|
+
help="Environment variables to pass to Docker container",
|
|
456
|
+
rich_help_panel="Docker Settings",
|
|
457
|
+
),
|
|
458
|
+
] = None,
|
|
459
|
+
docker_volume: Annotated[
|
|
460
|
+
list[str] | None,
|
|
461
|
+
typer.Option(
|
|
462
|
+
"--docker-volume",
|
|
463
|
+
"-v",
|
|
464
|
+
help="Volume mounts for Docker container",
|
|
465
|
+
rich_help_panel="Docker Settings",
|
|
466
|
+
),
|
|
467
|
+
] = None,
|
|
468
|
+
docker_arg: Annotated[
|
|
469
|
+
list[str] | None,
|
|
470
|
+
typer.Option(
|
|
471
|
+
"--docker-arg",
|
|
472
|
+
help="Additional arguments to pass to docker run",
|
|
473
|
+
rich_help_panel="Docker Settings",
|
|
474
|
+
),
|
|
475
|
+
] = None,
|
|
476
|
+
docker_home: Annotated[
|
|
477
|
+
str | None,
|
|
478
|
+
typer.Option(
|
|
479
|
+
"--docker-home",
|
|
480
|
+
help="Override the home directory for Docker",
|
|
481
|
+
rich_help_panel="Docker Settings",
|
|
482
|
+
),
|
|
483
|
+
] = None,
|
|
484
|
+
docker_workspace: Annotated[
|
|
485
|
+
str | None,
|
|
486
|
+
typer.Option(
|
|
487
|
+
"--docker-workspace",
|
|
488
|
+
help="Override the workspace directory for Docker",
|
|
489
|
+
rich_help_panel="Docker Settings",
|
|
490
|
+
),
|
|
491
|
+
] = None,
|
|
492
|
+
user_mapping_enabled: Annotated[
|
|
493
|
+
bool | None,
|
|
494
|
+
typer.Option(
|
|
495
|
+
"--user-mapping/--no-user-mapping",
|
|
496
|
+
help="Enable user mapping for Docker",
|
|
497
|
+
rich_help_panel="Docker Settings",
|
|
498
|
+
),
|
|
499
|
+
] = None,
|
|
500
|
+
user_uid: Annotated[
|
|
501
|
+
int | None,
|
|
502
|
+
typer.Option(
|
|
503
|
+
"--user-uid",
|
|
504
|
+
help="User UID for Docker user mapping",
|
|
505
|
+
rich_help_panel="Docker Settings",
|
|
506
|
+
),
|
|
507
|
+
] = None,
|
|
508
|
+
user_gid: Annotated[
|
|
509
|
+
int | None,
|
|
510
|
+
typer.Option(
|
|
511
|
+
"--user-gid",
|
|
512
|
+
help="User GID for Docker user mapping",
|
|
513
|
+
rich_help_panel="Docker Settings",
|
|
514
|
+
),
|
|
515
|
+
] = None,
|
|
345
516
|
) -> None:
|
|
346
517
|
"""
|
|
347
518
|
Start the CCProxy API server.
|
|
@@ -496,26 +667,95 @@ def api(
|
|
|
496
667
|
|
|
497
668
|
|
|
498
669
|
def claude(
|
|
499
|
-
args:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
670
|
+
args: Annotated[
|
|
671
|
+
list[str] | None,
|
|
672
|
+
typer.Argument(
|
|
673
|
+
help="Arguments to pass to claude CLI (e.g. --version, doctor, config)",
|
|
674
|
+
),
|
|
675
|
+
] = None,
|
|
676
|
+
docker: Annotated[
|
|
677
|
+
bool,
|
|
678
|
+
typer.Option(
|
|
679
|
+
"--docker",
|
|
680
|
+
"-d",
|
|
681
|
+
help="Run claude command from docker image instead of local CLI",
|
|
682
|
+
),
|
|
683
|
+
] = False,
|
|
509
684
|
# Docker settings using shared parameters
|
|
510
|
-
docker_image:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
685
|
+
docker_image: Annotated[
|
|
686
|
+
str | None,
|
|
687
|
+
typer.Option(
|
|
688
|
+
"--docker-image",
|
|
689
|
+
help="Docker image to use (overrides configuration)",
|
|
690
|
+
rich_help_panel="Docker Settings",
|
|
691
|
+
),
|
|
692
|
+
] = None,
|
|
693
|
+
docker_env: Annotated[
|
|
694
|
+
list[str] | None,
|
|
695
|
+
typer.Option(
|
|
696
|
+
"--docker-env",
|
|
697
|
+
"-e",
|
|
698
|
+
help="Environment variables to pass to Docker container",
|
|
699
|
+
rich_help_panel="Docker Settings",
|
|
700
|
+
),
|
|
701
|
+
] = None,
|
|
702
|
+
docker_volume: Annotated[
|
|
703
|
+
list[str] | None,
|
|
704
|
+
typer.Option(
|
|
705
|
+
"--docker-volume",
|
|
706
|
+
"-v",
|
|
707
|
+
help="Volume mounts for Docker container",
|
|
708
|
+
rich_help_panel="Docker Settings",
|
|
709
|
+
),
|
|
710
|
+
] = None,
|
|
711
|
+
docker_arg: Annotated[
|
|
712
|
+
list[str] | None,
|
|
713
|
+
typer.Option(
|
|
714
|
+
"--docker-arg",
|
|
715
|
+
help="Additional arguments to pass to docker run",
|
|
716
|
+
rich_help_panel="Docker Settings",
|
|
717
|
+
),
|
|
718
|
+
] = None,
|
|
719
|
+
docker_home: Annotated[
|
|
720
|
+
str | None,
|
|
721
|
+
typer.Option(
|
|
722
|
+
"--docker-home",
|
|
723
|
+
help="Override the home directory for Docker",
|
|
724
|
+
rich_help_panel="Docker Settings",
|
|
725
|
+
),
|
|
726
|
+
] = None,
|
|
727
|
+
docker_workspace: Annotated[
|
|
728
|
+
str | None,
|
|
729
|
+
typer.Option(
|
|
730
|
+
"--docker-workspace",
|
|
731
|
+
help="Override the workspace directory for Docker",
|
|
732
|
+
rich_help_panel="Docker Settings",
|
|
733
|
+
),
|
|
734
|
+
] = None,
|
|
735
|
+
user_mapping_enabled: Annotated[
|
|
736
|
+
bool | None,
|
|
737
|
+
typer.Option(
|
|
738
|
+
"--user-mapping/--no-user-mapping",
|
|
739
|
+
help="Enable user mapping for Docker",
|
|
740
|
+
rich_help_panel="Docker Settings",
|
|
741
|
+
),
|
|
742
|
+
] = None,
|
|
743
|
+
user_uid: Annotated[
|
|
744
|
+
int | None,
|
|
745
|
+
typer.Option(
|
|
746
|
+
"--user-uid",
|
|
747
|
+
help="User UID for Docker user mapping",
|
|
748
|
+
rich_help_panel="Docker Settings",
|
|
749
|
+
),
|
|
750
|
+
] = None,
|
|
751
|
+
user_gid: Annotated[
|
|
752
|
+
int | None,
|
|
753
|
+
typer.Option(
|
|
754
|
+
"--user-gid",
|
|
755
|
+
help="User GID for Docker user mapping",
|
|
756
|
+
rich_help_panel="Docker Settings",
|
|
757
|
+
),
|
|
758
|
+
] = None,
|
|
519
759
|
) -> None:
|
|
520
760
|
"""
|
|
521
761
|
Execute claude CLI commands directly.
|
ccproxy/cli/main.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Main entry point for CCProxy API Server."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import os
|
|
5
4
|
import secrets
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Optional, cast
|
|
6
|
+
from typing import Annotated, Any, Optional, cast
|
|
8
7
|
|
|
9
8
|
import typer
|
|
10
9
|
from click import get_current_context
|
|
@@ -26,14 +25,10 @@ from ccproxy.config.settings import (
|
|
|
26
25
|
)
|
|
27
26
|
from ccproxy.core.async_utils import get_package_dir, get_root_package_name
|
|
28
27
|
from ccproxy.core.logging import setup_logging
|
|
29
|
-
from ccproxy.models.responses import (
|
|
30
|
-
PermissionToolAllowResponse,
|
|
31
|
-
PermissionToolDenyResponse,
|
|
32
|
-
)
|
|
33
28
|
|
|
34
29
|
from .commands.auth import app as auth_app
|
|
35
30
|
from .commands.config import app as config_app
|
|
36
|
-
from .commands.serve import api
|
|
31
|
+
from .commands.serve import api
|
|
37
32
|
|
|
38
33
|
|
|
39
34
|
def version_callback(value: bool) -> None:
|
|
@@ -49,6 +44,7 @@ app = typer.Typer(
|
|
|
49
44
|
add_completion=True,
|
|
50
45
|
no_args_is_help=False,
|
|
51
46
|
pretty_exceptions_enable=False,
|
|
47
|
+
invoke_without_command=True,
|
|
52
48
|
)
|
|
53
49
|
|
|
54
50
|
# Logger will be configured by configuration manager
|
|
@@ -59,30 +55,42 @@ logger = get_logger(__name__)
|
|
|
59
55
|
@app.callback()
|
|
60
56
|
def app_main(
|
|
61
57
|
ctx: typer.Context,
|
|
62
|
-
version:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
version: Annotated[
|
|
59
|
+
bool,
|
|
60
|
+
typer.Option(
|
|
61
|
+
"--version",
|
|
62
|
+
"-V",
|
|
63
|
+
callback=version_callback,
|
|
64
|
+
is_eager=True,
|
|
65
|
+
help="Show version and exit.",
|
|
66
|
+
),
|
|
67
|
+
] = False,
|
|
68
|
+
config: Annotated[
|
|
69
|
+
Path | None,
|
|
70
|
+
typer.Option(
|
|
71
|
+
"--config",
|
|
72
|
+
"-c",
|
|
73
|
+
help="Path to configuration file (TOML, JSON, or YAML)",
|
|
74
|
+
exists=True,
|
|
75
|
+
file_okay=True,
|
|
76
|
+
dir_okay=False,
|
|
77
|
+
readable=True,
|
|
78
|
+
),
|
|
79
|
+
] = None,
|
|
80
80
|
) -> None:
|
|
81
81
|
"""CCProxy API Server - Anthropic and OpenAI compatible interface for Claude."""
|
|
82
82
|
# Store config path for commands to use
|
|
83
83
|
ctx.ensure_object(dict)
|
|
84
84
|
ctx.obj["config_path"] = config
|
|
85
85
|
|
|
86
|
+
# If no command is invoked, run the serve command by default
|
|
87
|
+
if ctx.invoked_subcommand is None:
|
|
88
|
+
# Import here to avoid circular imports
|
|
89
|
+
from .commands.serve import api
|
|
90
|
+
|
|
91
|
+
# Invoke the serve command
|
|
92
|
+
ctx.invoke(api)
|
|
93
|
+
|
|
86
94
|
|
|
87
95
|
# Register config command
|
|
88
96
|
app.add_typer(config_app)
|
|
@@ -96,92 +104,6 @@ app.command(name="serve")(api)
|
|
|
96
104
|
# Claude command removed - functionality moved to serve command
|
|
97
105
|
|
|
98
106
|
|
|
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
107
|
def main() -> None:
|
|
186
108
|
"""Entry point for the CLI application."""
|
|
187
109
|
app()
|