kalibr 1.0.28__py3-none-any.whl → 1.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.
- kalibr/__init__.py +170 -3
- kalibr/__main__.py +3 -203
- kalibr/capsule_middleware.py +108 -0
- kalibr/cli/__init__.py +5 -0
- kalibr/cli/capsule_cmd.py +174 -0
- kalibr/cli/deploy_cmd.py +114 -0
- kalibr/cli/main.py +67 -0
- kalibr/cli/run.py +200 -0
- kalibr/cli/serve.py +59 -0
- kalibr/client.py +293 -0
- kalibr/collector.py +173 -0
- kalibr/context.py +132 -0
- kalibr/cost_adapter.py +222 -0
- kalibr/decorators.py +140 -0
- kalibr/instrumentation/__init__.py +13 -0
- kalibr/instrumentation/anthropic_instr.py +282 -0
- kalibr/instrumentation/base.py +108 -0
- kalibr/instrumentation/google_instr.py +281 -0
- kalibr/instrumentation/openai_instr.py +265 -0
- kalibr/instrumentation/registry.py +153 -0
- kalibr/kalibr.py +144 -230
- kalibr/kalibr_app.py +53 -314
- kalibr/middleware/__init__.py +5 -0
- kalibr/middleware/auto_tracer.py +356 -0
- kalibr/models.py +41 -0
- kalibr/redaction.py +44 -0
- kalibr/schemas.py +116 -0
- kalibr/simple_tracer.py +255 -0
- kalibr/tokens.py +52 -0
- kalibr/trace_capsule.py +296 -0
- kalibr/trace_models.py +201 -0
- kalibr/tracer.py +354 -0
- kalibr/types.py +25 -93
- kalibr/utils.py +198 -0
- kalibr-1.1.0.dist-info/METADATA +97 -0
- kalibr-1.1.0.dist-info/RECORD +40 -0
- kalibr-1.1.0.dist-info/entry_points.txt +2 -0
- kalibr-1.1.0.dist-info/licenses/LICENSE +21 -0
- kalibr/deployment.py +0 -41
- kalibr/packager.py +0 -43
- kalibr/runtime_router.py +0 -138
- kalibr/schema_generators.py +0 -159
- kalibr/validator.py +0 -70
- kalibr-1.0.28.data/data/examples/README.md +0 -173
- kalibr-1.0.28.data/data/examples/basic_kalibr_example.py +0 -66
- kalibr-1.0.28.data/data/examples/enhanced_kalibr_example.py +0 -347
- kalibr-1.0.28.dist-info/METADATA +0 -175
- kalibr-1.0.28.dist-info/RECORD +0 -19
- kalibr-1.0.28.dist-info/entry_points.txt +0 -2
- kalibr-1.0.28.dist-info/licenses/LICENSE +0 -11
- {kalibr-1.0.28.dist-info → kalibr-1.1.0.dist-info}/WHEEL +0 -0
- {kalibr-1.0.28.dist-info → kalibr-1.1.0.dist-info}/top_level.txt +0 -0
kalibr/cli/deploy_cmd.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
app = typer.Typer(no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
# Utility: Ensure Flyctl CLI exists
|
|
15
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
def _ensure_flyctl():
|
|
17
|
+
"""Check that Flyctl CLI is installed and in PATH."""
|
|
18
|
+
os.environ["PATH"] += os.pathsep + os.path.expanduser("~/.fly/bin")
|
|
19
|
+
fly_path = shutil.which("flyctl") or shutil.which("fly")
|
|
20
|
+
if not fly_path:
|
|
21
|
+
console.print(
|
|
22
|
+
"❌ [red]Flyctl CLI not found. Install it from https://fly.io/docs/hands-on/install-flyctl/[/red]"
|
|
23
|
+
)
|
|
24
|
+
raise typer.Exit(code=1)
|
|
25
|
+
return fly_path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
# Main deploy command
|
|
30
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
@app.command()
|
|
32
|
+
def deploy(
|
|
33
|
+
file_path: str = typer.Argument(..., help="Path to your Python app file."),
|
|
34
|
+
runtime: str = typer.Option("fly", "--runtime", help="Runtime target (fly, render, or local)."),
|
|
35
|
+
app_name: str = typer.Option("kalibr-app", "--app-name", help="Name of the Fly.io app."),
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Deploy a Kalibr app to Fly.io or local runtime.
|
|
39
|
+
"""
|
|
40
|
+
console.print(
|
|
41
|
+
f"[bold cyan]─────────────────────────── Deploying {file_path} ───────────────────────────[/bold cyan]"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Verify file exists
|
|
45
|
+
app_path = Path(file_path).resolve()
|
|
46
|
+
if not app_path.exists():
|
|
47
|
+
console.print(f"❌ [red]File not found: {app_path}[/red]")
|
|
48
|
+
raise typer.Exit(code=1)
|
|
49
|
+
|
|
50
|
+
# ── Dockerfile ──────────────────────────────────────────────────────────────
|
|
51
|
+
dockerfile_path = app_path.parent / "Dockerfile"
|
|
52
|
+
if not dockerfile_path.exists():
|
|
53
|
+
console.print("🧱 Generating Dockerfile ...")
|
|
54
|
+
dockerfile_path.write_text(
|
|
55
|
+
f"""FROM python:3.11-slim
|
|
56
|
+
WORKDIR /app
|
|
57
|
+
COPY . .
|
|
58
|
+
RUN pip install kalibr fastapi uvicorn
|
|
59
|
+
CMD ["kalibr", "serve", "{app_path.name}"]
|
|
60
|
+
"""
|
|
61
|
+
)
|
|
62
|
+
console.print(f"✅ Created [green]{dockerfile_path}[/green]")
|
|
63
|
+
|
|
64
|
+
# ── fly.toml ────────────────────────────────────────────────────────────────
|
|
65
|
+
fly_toml_path = app_path.parent / "fly.toml"
|
|
66
|
+
fly_toml_contents = f"""
|
|
67
|
+
app = "{app_name}"
|
|
68
|
+
primary_region = "iad"
|
|
69
|
+
|
|
70
|
+
[build]
|
|
71
|
+
image = "python:3.11"
|
|
72
|
+
|
|
73
|
+
[env]
|
|
74
|
+
PORT = "8000"
|
|
75
|
+
|
|
76
|
+
[[services]]
|
|
77
|
+
internal_port = 8000
|
|
78
|
+
protocol = "tcp"
|
|
79
|
+
|
|
80
|
+
[[services.ports]]
|
|
81
|
+
port = 80
|
|
82
|
+
handlers = ["http"]
|
|
83
|
+
|
|
84
|
+
[[services.ports]]
|
|
85
|
+
port = 443
|
|
86
|
+
handlers = ["tls", "http"]
|
|
87
|
+
""".strip()
|
|
88
|
+
|
|
89
|
+
fly_toml_path.write_text(fly_toml_contents)
|
|
90
|
+
console.print(
|
|
91
|
+
f"✅ Ensured [green]{fly_toml_path}[/green] includes app name: [bold]{app_name}[/bold]"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# ── Deploy ─────────────────────────────────────────────────────────────────
|
|
95
|
+
fly_path = _ensure_flyctl()
|
|
96
|
+
console.print(f"🚀 Deploying to Fly.io using [green]{fly_path}[/green]...")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
subprocess.run(["flyctl", "deploy", "--config", str(fly_toml_path)], check=True)
|
|
100
|
+
console.print(f"✅ [bold green]Deployment complete![/bold green]")
|
|
101
|
+
console.print(f"🌍 Visit your app at [underline]https://{app_name}.fly.dev[/underline]")
|
|
102
|
+
except subprocess.CalledProcessError:
|
|
103
|
+
console.print(f"❌ [red]Fly.io deployment failed[/red]")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
# Expose CLI entrypoint
|
|
108
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
109
|
+
def get_app():
|
|
110
|
+
return app
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# Keep compatibility for `from kalibr.cli.deploy_cmd import deploy`
|
|
114
|
+
deploy = deploy
|
kalibr/cli/main.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Main CLI entry point for Kalibr"""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from kalibr.cli.capsule_cmd import capsule
|
|
5
|
+
from kalibr.cli.deploy_cmd import deploy
|
|
6
|
+
from kalibr.cli.run import run
|
|
7
|
+
|
|
8
|
+
# Import command functions
|
|
9
|
+
from kalibr.cli.serve import serve
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
name="kalibr",
|
|
14
|
+
help="Kalibr SDK - Multi-Model AI Integration Framework",
|
|
15
|
+
add_completion=False,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
# Register commands directly
|
|
21
|
+
app.command(name="serve")(serve)
|
|
22
|
+
app.command(name="deploy")(deploy)
|
|
23
|
+
app.command(name="capsule")(capsule)
|
|
24
|
+
app.command(name="run")(run)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def version():
|
|
29
|
+
"""Show Kalibr SDK version"""
|
|
30
|
+
from kalibr import __version__
|
|
31
|
+
|
|
32
|
+
console.print(f"[bold]Kalibr SDK version:[/bold] {__version__}")
|
|
33
|
+
console.print("Enhanced multi-model AI integration framework")
|
|
34
|
+
console.print("Supports: GPT Actions, Claude MCP, Gemini Extensions, Copilot Plugins")
|
|
35
|
+
console.print("GitHub: https://github.com/devonakelley/kalibr-sdk")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def package():
|
|
40
|
+
"""Create a deployable MCP bundle (code + schemas + metadata)."""
|
|
41
|
+
console.print("[yellow]📦 Package feature coming soon[/yellow]")
|
|
42
|
+
console.print("This will create a deployment bundle with all schemas.")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def validate():
|
|
47
|
+
"""Validate MCP manifest against minimal JSON schema & version hint."""
|
|
48
|
+
console.print("[yellow]✓ Validation feature coming soon[/yellow]")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command()
|
|
52
|
+
def update_schemas():
|
|
53
|
+
"""Stub: instruct users to upgrade SDK and regenerate manifests."""
|
|
54
|
+
console.print("[cyan]ℹ️ To update schemas:[/cyan]")
|
|
55
|
+
console.print("1. pip install --upgrade kalibr")
|
|
56
|
+
console.print("2. Restart your kalibr serve command")
|
|
57
|
+
console.print("3. Schemas will be auto-regenerated")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def status():
|
|
62
|
+
"""Check status of a deployed Kalibr app."""
|
|
63
|
+
console.print("[yellow]📊 Status checking coming soon[/yellow]")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
app()
|
kalibr/cli/run.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kalibr run - Managed runtime lifecycle for MCP servers
|
|
3
|
+
Phase 3E - CLI Command
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import atexit
|
|
7
|
+
import os
|
|
8
|
+
import signal
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run(
|
|
24
|
+
file_path: str = typer.Argument(..., help="Path to your Python agent file"),
|
|
25
|
+
runtime: str = typer.Option(
|
|
26
|
+
"local", "--runtime", "-r", help="Runtime provider (local, fly.io)"
|
|
27
|
+
),
|
|
28
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind to (local only)"),
|
|
29
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to bind to (local only)"),
|
|
30
|
+
backend_url: Optional[str] = typer.Option(None, "--backend-url", help="Kalibr backend URL"),
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Run a Kalibr agent with managed runtime lifecycle.
|
|
34
|
+
|
|
35
|
+
Automatically:
|
|
36
|
+
- Registers runtime in backend
|
|
37
|
+
- Injects tracing middleware
|
|
38
|
+
- Emits capsules on completion
|
|
39
|
+
- Updates runtime status
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
|
|
43
|
+
# Run locally
|
|
44
|
+
kalibr run weather.py
|
|
45
|
+
|
|
46
|
+
# Run on Fly.io
|
|
47
|
+
kalibr run weather.py --runtime fly.io
|
|
48
|
+
|
|
49
|
+
# Custom backend
|
|
50
|
+
kalibr run weather.py --backend-url https://api.kalibr.io
|
|
51
|
+
"""
|
|
52
|
+
# Validate file exists
|
|
53
|
+
agent_path = Path(file_path).resolve()
|
|
54
|
+
if not agent_path.exists():
|
|
55
|
+
console.print(f"[red]❌ Error: File '{file_path}' not found[/red]")
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
|
|
58
|
+
# Configure backend
|
|
59
|
+
backend = backend_url or os.getenv("KALIBR_BACKEND_URL", "http://localhost:8001")
|
|
60
|
+
api_key = os.getenv("KALIBR_API_KEY", "test_key_12345")
|
|
61
|
+
|
|
62
|
+
# Generate runtime metadata
|
|
63
|
+
runtime_id = str(uuid.uuid4())
|
|
64
|
+
context_token = str(uuid.uuid4())
|
|
65
|
+
agent_name = agent_path.stem
|
|
66
|
+
|
|
67
|
+
console.print(f"[bold cyan]🚀 Starting Kalibr Runtime[/bold cyan]")
|
|
68
|
+
console.print(f"[dim]Agent:[/dim] {agent_name}")
|
|
69
|
+
console.print(f"[dim]Runtime ID:[/dim] {runtime_id}")
|
|
70
|
+
console.print(f"[dim]Context Token:[/dim] {context_token}")
|
|
71
|
+
console.print(f"[dim]Provider:[/dim] {runtime}")
|
|
72
|
+
|
|
73
|
+
# Register runtime
|
|
74
|
+
try:
|
|
75
|
+
with httpx.Client(timeout=10.0) as client:
|
|
76
|
+
response = client.post(
|
|
77
|
+
f"{backend}/api/runtimes/register",
|
|
78
|
+
json={
|
|
79
|
+
"runtime_id": runtime_id,
|
|
80
|
+
"agent_name": agent_name,
|
|
81
|
+
"runtime_provider": runtime,
|
|
82
|
+
"context_token": context_token,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
response.raise_for_status()
|
|
86
|
+
console.print(f"[green]✓[/green] Runtime registered")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
console.print(f"[yellow]⚠️ Failed to register runtime: {e}[/yellow]")
|
|
89
|
+
console.print("[yellow]Continuing without backend registration...[/yellow]")
|
|
90
|
+
|
|
91
|
+
# Setup environment variables
|
|
92
|
+
env = os.environ.copy()
|
|
93
|
+
env.update(
|
|
94
|
+
{
|
|
95
|
+
"KALIBR_RUNTIME_ID": runtime_id,
|
|
96
|
+
"KALIBR_CONTEXT_TOKEN": context_token,
|
|
97
|
+
"KALIBR_TRACE_ENABLED": "true",
|
|
98
|
+
"KALIBR_COLLECTOR_URL": f"{backend}/api/ingest",
|
|
99
|
+
"KALIBR_API_KEY": api_key,
|
|
100
|
+
"KALIBR_BACKEND_URL": backend,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Store runtime metadata for cleanup
|
|
105
|
+
runtime_metadata = {
|
|
106
|
+
"runtime_id": runtime_id,
|
|
107
|
+
"backend": backend,
|
|
108
|
+
"api_key": api_key,
|
|
109
|
+
"start_time": time.time(),
|
|
110
|
+
"trace_count": 0,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Register cleanup handler
|
|
114
|
+
def cleanup_runtime():
|
|
115
|
+
"""Stop runtime and update backend"""
|
|
116
|
+
try:
|
|
117
|
+
duration = time.time() - runtime_metadata["start_time"]
|
|
118
|
+
|
|
119
|
+
with httpx.Client(timeout=10.0) as client:
|
|
120
|
+
client.patch(
|
|
121
|
+
f"{backend}/api/runtimes/{runtime_id}/stop",
|
|
122
|
+
json={
|
|
123
|
+
"total_traces": runtime_metadata.get("trace_count", 0),
|
|
124
|
+
"total_cost_usd": 0.0, # Will be updated by capsule emission
|
|
125
|
+
"total_latency_ms": int(duration * 1000),
|
|
126
|
+
},
|
|
127
|
+
headers={"X-API-Key": api_key},
|
|
128
|
+
)
|
|
129
|
+
console.print(f"[green]✓[/green] Runtime stopped")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
console.print(f"[yellow]⚠️ Failed to stop runtime: {e}[/yellow]")
|
|
132
|
+
|
|
133
|
+
atexit.register(cleanup_runtime)
|
|
134
|
+
|
|
135
|
+
# Launch based on runtime provider
|
|
136
|
+
if runtime == "local":
|
|
137
|
+
# Local subprocess execution
|
|
138
|
+
console.print(f"\n[bold]Starting agent on {host}:{port}[/bold]")
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
# Use kalibr serve to run the agent
|
|
142
|
+
cmd = [
|
|
143
|
+
sys.executable,
|
|
144
|
+
"-m",
|
|
145
|
+
"kalibr.cli.main",
|
|
146
|
+
"serve",
|
|
147
|
+
str(agent_path),
|
|
148
|
+
"--host",
|
|
149
|
+
host,
|
|
150
|
+
"--port",
|
|
151
|
+
str(port),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
process = subprocess.Popen(
|
|
155
|
+
cmd,
|
|
156
|
+
env=env,
|
|
157
|
+
stdout=subprocess.PIPE,
|
|
158
|
+
stderr=subprocess.STDOUT,
|
|
159
|
+
text=True,
|
|
160
|
+
bufsize=1,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Stream output
|
|
164
|
+
console.print("[dim]─" * 80 + "[/dim]")
|
|
165
|
+
for line in process.stdout:
|
|
166
|
+
print(line, end="")
|
|
167
|
+
|
|
168
|
+
process.wait()
|
|
169
|
+
|
|
170
|
+
except KeyboardInterrupt:
|
|
171
|
+
console.print("\n[yellow]⚠️ Received interrupt, stopping runtime...[/yellow]")
|
|
172
|
+
process.terminate()
|
|
173
|
+
process.wait(timeout=5)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
console.print(f"[red]❌ Runtime error: {e}[/red]")
|
|
176
|
+
raise typer.Exit(1)
|
|
177
|
+
|
|
178
|
+
elif runtime == "fly.io":
|
|
179
|
+
# Fly.io deployment
|
|
180
|
+
console.print(f"\n[bold]Deploying to Fly.io[/bold]")
|
|
181
|
+
|
|
182
|
+
# Use existing deploy command
|
|
183
|
+
from kalibr.cli.deploy_cmd import deploy
|
|
184
|
+
|
|
185
|
+
deploy(
|
|
186
|
+
file_path=str(agent_path),
|
|
187
|
+
runtime="fly",
|
|
188
|
+
app_name=f"kalibr-{agent_name}",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
console.print(f"[green]✓[/green] Deployed to Fly.io")
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
console.print(f"[red]❌ Unsupported runtime: {runtime}[/red]")
|
|
195
|
+
console.print("[yellow]Supported runtimes: local, fly.io[/yellow]")
|
|
196
|
+
raise typer.Exit(1)
|
|
197
|
+
|
|
198
|
+
# Cleanup
|
|
199
|
+
cleanup_runtime()
|
|
200
|
+
console.print("\n[bold green]✓ Runtime completed[/bold green]")
|
kalibr/cli/serve.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Serve command for local development"""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def serve(
|
|
13
|
+
file_path: str = typer.Argument(..., help="Path to your Kalibr app (e.g. app.py)"),
|
|
14
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind to"),
|
|
15
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to bind to"),
|
|
16
|
+
):
|
|
17
|
+
"""Serve a Kalibr-powered API locally."""
|
|
18
|
+
path = Path(file_path)
|
|
19
|
+
|
|
20
|
+
if not path.exists():
|
|
21
|
+
console.print(f"[red]❌ Error: File '{file_path}' not found[/red]")
|
|
22
|
+
raise typer.Exit(1)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
# Load the module
|
|
26
|
+
spec = importlib.util.spec_from_file_location("user_app", path)
|
|
27
|
+
module = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(module)
|
|
29
|
+
|
|
30
|
+
# Find Kalibr app instance
|
|
31
|
+
app_instance = None
|
|
32
|
+
for attr_name in dir(module):
|
|
33
|
+
attr = getattr(module, attr_name)
|
|
34
|
+
if hasattr(attr, "app") and hasattr(attr.app, "openapi"):
|
|
35
|
+
app_instance = attr
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
if not app_instance:
|
|
39
|
+
console.print(f"[red]❌ Error loading {file_path}: No Kalibr app found[/red]")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
# Display startup info
|
|
43
|
+
console.print(f"[bold green]🚀 Starting Kalibr server from {path.name}[/bold green]")
|
|
44
|
+
display_host = "localhost" if host == "0.0.0.0" else host
|
|
45
|
+
console.print(f"📍 GPT (OpenAPI): http://{display_host}:{port}/openapi.json")
|
|
46
|
+
console.print(f"📍 Claude (MCP): http://{display_host}:{port}/mcp.json")
|
|
47
|
+
console.print(f"📍 Swagger UI: http://{display_host}:{port}/docs")
|
|
48
|
+
console.print(
|
|
49
|
+
f"🔌 Actions registered: {[action['name'] for action in app_instance.actions]}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Run the server
|
|
53
|
+
import uvicorn
|
|
54
|
+
|
|
55
|
+
uvicorn.run(app_instance.app, host=host, port=port)
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
console.print(f"[red]❌ Error loading {file_path}: {str(e)}[/red]")
|
|
59
|
+
raise typer.Exit(1)
|