kaos-cli 0.0.1__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.
kaos_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """KAOS CLI - CLI for K8s Agent Orchestration System."""
2
+
3
+ __version__ = "0.1.0"
kaos_cli/install.py ADDED
@@ -0,0 +1,121 @@
1
+ """KAOS install/uninstall commands for the Kubernetes operator."""
2
+
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+
7
+ import typer
8
+
9
+ # Helm chart repository URL (hosted on GitHub Pages)
10
+ HELM_REPO_URL = "https://axsaucedo.github.io/kaos/charts"
11
+ HELM_REPO_NAME = "kaos"
12
+ HELM_CHART_NAME = "kaos-operator"
13
+ DEFAULT_NAMESPACE = "kaos-system"
14
+ DEFAULT_RELEASE_NAME = "kaos"
15
+
16
+
17
+ def check_helm_installed() -> bool:
18
+ """Check if helm is installed and available."""
19
+ return shutil.which("helm") is not None
20
+
21
+
22
+ def run_helm_command(args: list[str], check: bool = True) -> subprocess.CompletedProcess:
23
+ """Run a helm command and return the result."""
24
+ cmd = ["helm"] + args
25
+ try:
26
+ result = subprocess.run(
27
+ cmd,
28
+ capture_output=True,
29
+ text=True,
30
+ check=check,
31
+ )
32
+ return result
33
+ except subprocess.CalledProcessError as e:
34
+ typer.echo(f"Error running helm: {e.stderr}", err=True)
35
+ raise
36
+
37
+
38
+ def install_command(
39
+ namespace: str,
40
+ release_name: str,
41
+ version: str | None,
42
+ set_values: list[str],
43
+ wait: bool,
44
+ ) -> None:
45
+ """Install the KAOS operator using Helm."""
46
+ if not check_helm_installed():
47
+ typer.echo("Error: helm is not installed. Please install helm first.", err=True)
48
+ typer.echo("See: https://helm.sh/docs/intro/install/", err=True)
49
+ sys.exit(1)
50
+
51
+ typer.echo(f"Installing KAOS operator to namespace '{namespace}'...")
52
+
53
+ # Add the Helm repository
54
+ typer.echo(f"Adding Helm repository '{HELM_REPO_NAME}'...")
55
+ result = run_helm_command(
56
+ ["repo", "add", HELM_REPO_NAME, HELM_REPO_URL, "--force-update"],
57
+ check=False,
58
+ )
59
+ if result.returncode != 0 and "already exists" not in result.stderr:
60
+ typer.echo(f"Warning: {result.stderr}", err=True)
61
+
62
+ # Update repositories
63
+ typer.echo("Updating Helm repositories...")
64
+ run_helm_command(["repo", "update"], check=False)
65
+
66
+ # Build helm install command
67
+ helm_args = [
68
+ "upgrade",
69
+ "--install",
70
+ release_name,
71
+ f"{HELM_REPO_NAME}/{HELM_CHART_NAME}",
72
+ "--namespace",
73
+ namespace,
74
+ "--create-namespace",
75
+ ]
76
+
77
+ if version:
78
+ helm_args.extend(["--version", version])
79
+
80
+ if wait:
81
+ helm_args.append("--wait")
82
+
83
+ for value in set_values:
84
+ helm_args.extend(["--set", value])
85
+
86
+ typer.echo(f"Installing chart {HELM_CHART_NAME}...")
87
+ result = run_helm_command(helm_args)
88
+
89
+ if result.returncode == 0:
90
+ typer.echo("")
91
+ typer.echo("✅ KAOS operator installed successfully!")
92
+ typer.echo("")
93
+ typer.echo("Next steps:")
94
+ typer.echo(f" 1. Check the operator status: kubectl get pods -n {namespace}")
95
+ typer.echo(" 2. Create your first agent: kubectl apply -f your-agent.yaml")
96
+ typer.echo(" 3. Open the UI: kaos ui")
97
+ else:
98
+ typer.echo(f"Error: {result.stderr}", err=True)
99
+ sys.exit(1)
100
+
101
+
102
+ def uninstall_command(namespace: str, release_name: str) -> None:
103
+ """Uninstall the KAOS operator using Helm."""
104
+ if not check_helm_installed():
105
+ typer.echo("Error: helm is not installed.", err=True)
106
+ sys.exit(1)
107
+
108
+ typer.echo(f"Uninstalling KAOS operator from namespace '{namespace}'...")
109
+
110
+ result = run_helm_command(
111
+ ["uninstall", release_name, "--namespace", namespace],
112
+ check=False,
113
+ )
114
+
115
+ if result.returncode == 0:
116
+ typer.echo("✅ KAOS operator uninstalled successfully!")
117
+ elif "not found" in result.stderr.lower():
118
+ typer.echo(f"Release '{release_name}' not found in namespace '{namespace}'.")
119
+ else:
120
+ typer.echo(f"Error: {result.stderr}", err=True)
121
+ sys.exit(1)
kaos_cli/main.py ADDED
@@ -0,0 +1,117 @@
1
+ """KAOS CLI main entry point."""
2
+
3
+ from typing import List
4
+
5
+ import typer
6
+
7
+ from kaos_cli.install import (
8
+ DEFAULT_NAMESPACE,
9
+ DEFAULT_RELEASE_NAME,
10
+ install_command,
11
+ uninstall_command,
12
+ )
13
+ from kaos_cli.ui import ui_command
14
+
15
+ # Disable shell completion message
16
+ app = typer.Typer(
17
+ add_completion=False,
18
+ help="KAOS - K8s Agent Orchestration System CLI",
19
+ no_args_is_help=True,
20
+ )
21
+
22
+
23
+ @app.command(name="ui")
24
+ def ui(
25
+ k8s_url: str = typer.Option(
26
+ None,
27
+ "--k8s-url",
28
+ help="Kubernetes API server URL. If not provided, uses kubeconfig.",
29
+ ),
30
+ expose_port: int = typer.Option(
31
+ 8010,
32
+ "--expose-port",
33
+ help="Port to expose the CORS proxy on.",
34
+ ),
35
+ namespace: str = typer.Option(
36
+ "default",
37
+ "--namespace",
38
+ "-n",
39
+ help="Initial namespace to display in the UI.",
40
+ ),
41
+ no_browser: bool = typer.Option(
42
+ False,
43
+ "--no-browser",
44
+ help="Don't automatically open the browser.",
45
+ ),
46
+ ) -> None:
47
+ """Start a CORS-enabled proxy and open the KAOS UI."""
48
+ ui_command(k8s_url=k8s_url, expose_port=expose_port, namespace=namespace, no_browser=no_browser)
49
+
50
+
51
+ @app.command(name="install")
52
+ def install(
53
+ namespace: str = typer.Option(
54
+ DEFAULT_NAMESPACE,
55
+ "--namespace",
56
+ "-n",
57
+ help="Kubernetes namespace to install into.",
58
+ ),
59
+ release_name: str = typer.Option(
60
+ DEFAULT_RELEASE_NAME,
61
+ "--release-name",
62
+ help="Helm release name.",
63
+ ),
64
+ version: str = typer.Option(
65
+ None,
66
+ "--version",
67
+ help="Chart version to install. Defaults to latest.",
68
+ ),
69
+ set_values: List[str] = typer.Option(
70
+ [],
71
+ "--set",
72
+ help="Set Helm values (can be used multiple times).",
73
+ ),
74
+ wait: bool = typer.Option(
75
+ False,
76
+ "--wait",
77
+ help="Wait for pods to be ready before returning.",
78
+ ),
79
+ ) -> None:
80
+ """Install the KAOS operator using Helm."""
81
+ install_command(
82
+ namespace=namespace,
83
+ release_name=release_name,
84
+ version=version,
85
+ set_values=list(set_values),
86
+ wait=wait,
87
+ )
88
+
89
+
90
+ @app.command(name="uninstall")
91
+ def uninstall(
92
+ namespace: str = typer.Option(
93
+ DEFAULT_NAMESPACE,
94
+ "--namespace",
95
+ "-n",
96
+ help="Kubernetes namespace to uninstall from.",
97
+ ),
98
+ release_name: str = typer.Option(
99
+ DEFAULT_RELEASE_NAME,
100
+ "--release-name",
101
+ help="Helm release name.",
102
+ ),
103
+ ) -> None:
104
+ """Uninstall the KAOS operator."""
105
+ uninstall_command(namespace=namespace, release_name=release_name)
106
+
107
+
108
+ @app.command(name="version")
109
+ def version() -> None:
110
+ """Show the KAOS CLI version."""
111
+ from kaos_cli import __version__
112
+
113
+ typer.echo(f"kaos-cli {__version__}")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ app()
kaos_cli/proxy.py ADDED
@@ -0,0 +1,105 @@
1
+ """CORS-enabled Kubernetes API proxy."""
2
+
3
+ import ssl
4
+
5
+ import httpx
6
+ from kubernetes import client, config
7
+ from starlette.applications import Starlette
8
+ from starlette.middleware.cors import CORSMiddleware
9
+ from starlette.requests import Request
10
+ from starlette.responses import Response
11
+ from starlette.routing import Route
12
+
13
+
14
+ def create_proxy_app(k8s_url: str | None = None) -> Starlette:
15
+ """Create a Starlette app that proxies requests to the K8s API with CORS."""
16
+ # Load kubernetes config
17
+ try:
18
+ config.load_incluster_config()
19
+ except config.ConfigException:
20
+ config.load_kube_config()
21
+
22
+ configuration = client.Configuration.get_default_copy()
23
+
24
+ # Use provided URL or from kubeconfig
25
+ api_url = k8s_url or configuration.host
26
+
27
+ # Get auth info from configuration
28
+ auth_headers: dict[str, str] = {}
29
+ if configuration.api_key and "authorization" in configuration.api_key:
30
+ auth_headers["Authorization"] = configuration.api_key["authorization"]
31
+ elif configuration.api_key_prefix and configuration.api_key:
32
+ for key, value in configuration.api_key.items():
33
+ prefix = configuration.api_key_prefix.get(key, "")
34
+ auth_headers["Authorization"] = f"{prefix} {value}".strip()
35
+ break
36
+
37
+ # SSL/TLS configuration - handle client certificates
38
+ ssl_context: ssl.SSLContext | bool = False
39
+ if configuration.cert_file and configuration.key_file:
40
+ ssl_context = ssl.create_default_context()
41
+ ssl_context.load_cert_chain(
42
+ certfile=configuration.cert_file,
43
+ keyfile=configuration.key_file,
44
+ )
45
+ if configuration.ssl_ca_cert:
46
+ ssl_context.load_verify_locations(cafile=configuration.ssl_ca_cert)
47
+ else:
48
+ ssl_context.check_hostname = False
49
+ ssl_context.verify_mode = ssl.CERT_NONE
50
+ elif configuration.ssl_ca_cert:
51
+ ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert)
52
+
53
+ async def proxy_request(request: Request) -> Response:
54
+ """Proxy incoming requests to the Kubernetes API server."""
55
+ path = request.url.path
56
+ query = str(request.url.query) if request.url.query else ""
57
+ target_url = f"{api_url}{path}"
58
+ if query:
59
+ target_url = f"{target_url}?{query}"
60
+
61
+ # Start with auth headers
62
+ headers = dict(auth_headers)
63
+
64
+ # Copy relevant headers from request
65
+ for key in ["content-type", "accept", "mcp-session-id"]:
66
+ if key in request.headers:
67
+ headers[key] = request.headers[key]
68
+
69
+ # Get request body
70
+ body = await request.body()
71
+
72
+ async with httpx.AsyncClient(verify=ssl_context, timeout=120.0) as http_client:
73
+ response = await http_client.request(
74
+ method=request.method,
75
+ url=target_url,
76
+ headers=headers,
77
+ content=body if body else None,
78
+ )
79
+
80
+ # Build response headers
81
+ response_headers = dict(response.headers)
82
+
83
+ return Response(
84
+ content=response.content,
85
+ status_code=response.status_code,
86
+ headers=response_headers,
87
+ media_type=response.headers.get("content-type"),
88
+ )
89
+
90
+ routes = [
91
+ Route("/{path:path}", proxy_request, methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]),
92
+ ]
93
+
94
+ app = Starlette(routes=routes)
95
+
96
+ # Add CORS middleware with mcp-session-id exposed
97
+ app.add_middleware(
98
+ CORSMiddleware,
99
+ allow_origins=["*"],
100
+ allow_methods=["*"],
101
+ allow_headers=["*"],
102
+ expose_headers=["mcp-session-id"],
103
+ )
104
+
105
+ return app
kaos_cli/ui.py ADDED
@@ -0,0 +1,57 @@
1
+ """KAOS UI command - starts a CORS-enabled K8s API proxy."""
2
+
3
+ import signal
4
+ import sys
5
+ import threading
6
+ import time
7
+ import webbrowser
8
+ from urllib.parse import urlencode
9
+
10
+ import typer
11
+ import uvicorn
12
+
13
+ # KAOS UI hosted on GitHub Pages
14
+ KAOS_UI_URL = "https://axsaucedo.github.io/kaos-ui/"
15
+
16
+
17
+ def ui_command(k8s_url: str | None, expose_port: int, namespace: str, no_browser: bool) -> None:
18
+ """Start a CORS-enabled proxy to the Kubernetes API server."""
19
+ from kaos_cli.proxy import create_proxy_app
20
+
21
+ app = create_proxy_app(k8s_url=k8s_url)
22
+
23
+ typer.echo(f"Starting KAOS UI proxy on http://localhost:{expose_port}")
24
+
25
+ # Build UI URL with query parameters
26
+ query_params = {}
27
+ # Only add kubernetesUrl if not using default port
28
+ if expose_port != 8010:
29
+ query_params["kubernetesUrl"] = f"http://localhost:{expose_port}"
30
+ # Only add namespace if not using default
31
+ if namespace and namespace != "default":
32
+ query_params["namespace"] = namespace
33
+
34
+ ui_url = KAOS_UI_URL
35
+ if query_params:
36
+ ui_url = f"{KAOS_UI_URL}?{urlencode(query_params)}"
37
+
38
+ typer.echo(f"KAOS UI: {ui_url}")
39
+ typer.echo("Press Ctrl+C to stop")
40
+
41
+ def handle_signal(signum: int, frame: object) -> None:
42
+ typer.echo("\nShutting down...")
43
+ sys.exit(0)
44
+
45
+ signal.signal(signal.SIGINT, handle_signal)
46
+ signal.signal(signal.SIGTERM, handle_signal)
47
+
48
+ # Open browser after a short delay to allow server to start
49
+ if not no_browser:
50
+ def open_browser() -> None:
51
+ time.sleep(1.5)
52
+ webbrowser.open(ui_url)
53
+
54
+ browser_thread = threading.Thread(target=open_browser, daemon=True)
55
+ browser_thread.start()
56
+
57
+ uvicorn.run(app, host="0.0.0.0", port=expose_port, log_level="info")
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: kaos-cli
3
+ Version: 0.0.1
4
+ Summary: CLI for KAOS (K8s Agent Orchestration System)
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer>=0.9.0
8
+ Requires-Dist: httpx>=0.27.0
9
+ Requires-Dist: uvicorn>=0.30.0
10
+ Requires-Dist: starlette>=0.37.0
11
+ Requires-Dist: kubernetes>=29.0.0
12
+
13
+ # KAOS CLI
14
+
15
+ Command-line interface for KAOS (K8s Agent Orchestration System).
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ cd kaos-cli
21
+ uv sync
22
+ source .venv/bin/activate
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Start UI Proxy
28
+
29
+ Start a CORS-enabled proxy to the Kubernetes API server:
30
+
31
+ ```bash
32
+ kaos ui
33
+ ```
34
+
35
+ This starts a local proxy on port 8010 that:
36
+ - Proxies requests to the Kubernetes API using your kubeconfig credentials
37
+ - Adds CORS headers to enable browser-based access
38
+ - Exposes the `mcp-session-id` header for MCP protocol support
39
+
40
+ Options:
41
+ - `--k8s-url`: Override the Kubernetes API URL (default: from kubeconfig)
42
+ - `--expose-port`: Port to expose the proxy on (default: 8010)
43
+ - `--namespace`, `-n`: Initial namespace to display in the UI (default: "default")
44
+ - `--no-browser`: Don't automatically open the browser
45
+
46
+ Example:
47
+ ```bash
48
+ # Use default settings
49
+ kaos ui
50
+
51
+ # Custom port
52
+ kaos ui --expose-port 9000
53
+
54
+ # Start with a specific namespace
55
+ kaos ui --namespace kaos-system
56
+
57
+ # Custom K8s URL
58
+ kaos ui --k8s-url https://my-cluster:6443
59
+ ```
60
+
61
+ ### Version
62
+
63
+ ```bash
64
+ kaos version
65
+ ```
66
+
67
+ ## Development
68
+
69
+ ```bash
70
+ # Run tests
71
+ pytest
72
+
73
+ # Run directly
74
+ python -m kaos_cli.main ui
75
+ ```
@@ -0,0 +1,10 @@
1
+ kaos_cli/__init__.py,sha256=1Yxq51Ckr8SnrwtSuCn9JKnQIJ0CDEh5CX2-zZh3CHQ,80
2
+ kaos_cli/install.py,sha256=B3n0moEM3RTV62u9p7GVUQYWgz8N2tkbkfJ6LlVMdW0,3693
3
+ kaos_cli/main.py,sha256=UpirI5tN_jgSYSfeRTQZNKzuAqv0A45LqRMLoepAyJ4,2833
4
+ kaos_cli/proxy.py,sha256=wLfxxPGxvv9yHX2mQ-FoLiQIXVJSvb2WwgEsfRVr1Qo,3702
5
+ kaos_cli/ui.py,sha256=PRXjdV58V3XM0Cyb2OuRJKXJxPigsCDvyiB5J5XAyGU,1764
6
+ kaos_cli-0.0.1.dist-info/METADATA,sha256=8IAz5Fk4j93JKiBA-m5WD1AA2m0sRKYad5CQdzilNRc,1462
7
+ kaos_cli-0.0.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
8
+ kaos_cli-0.0.1.dist-info/entry_points.txt,sha256=UhLExeu2qKkwddNEEm0o5TKtVqOkcIEcrWqyyt_UVl4,43
9
+ kaos_cli-0.0.1.dist-info/top_level.txt,sha256=33XlAB5b22FEtftzn0QyWXr6f8TNyh-XBIoRcQp0NRA,9
10
+ kaos_cli-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kaos = kaos_cli.main:app
@@ -0,0 +1 @@
1
+ kaos_cli