kalibr 1.0.21__py3-none-any.whl → 1.0.23__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/__main__.py +79 -550
- kalibr/deployment.py +36 -21
- kalibr/kalibr_app.py +32 -11
- kalibr/packager.py +43 -0
- kalibr/runtime_router.py +138 -0
- kalibr/schema_generators.py +21 -74
- kalibr/validator.py +70 -0
- kalibr-1.0.23.dist-info/METADATA +257 -0
- kalibr-1.0.23.dist-info/RECORD +19 -0
- kalibr-1.0.21.dist-info/METADATA +0 -302
- kalibr-1.0.21.dist-info/RECORD +0 -16
- {kalibr-1.0.21.data → kalibr-1.0.23.data}/data/examples/README.md +0 -0
- {kalibr-1.0.21.data → kalibr-1.0.23.data}/data/examples/basic_kalibr_example.py +0 -0
- {kalibr-1.0.21.data → kalibr-1.0.23.data}/data/examples/enhanced_kalibr_example.py +0 -0
- {kalibr-1.0.21.dist-info → kalibr-1.0.23.dist-info}/WHEEL +0 -0
- {kalibr-1.0.21.dist-info → kalibr-1.0.23.dist-info}/entry_points.txt +0 -0
- {kalibr-1.0.21.dist-info → kalibr-1.0.23.dist-info}/licenses/LICENSE +0 -0
- {kalibr-1.0.21.dist-info → kalibr-1.0.23.dist-info}/top_level.txt +0 -0
kalibr/__main__.py
CHANGED
|
@@ -6,67 +6,50 @@ from pathlib import Path
|
|
|
6
6
|
import os
|
|
7
7
|
import requests
|
|
8
8
|
import json
|
|
9
|
-
import zipfile
|
|
10
|
-
import tempfile
|
|
11
9
|
|
|
12
|
-
# Initialize a Typer application
|
|
13
10
|
app = typer.Typer()
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
def serve(
|
|
17
|
-
file: str = typer.Argument("kalibr_app.py", help="Python file with Kalibr app"),
|
|
18
|
-
host: str = typer.Option("0.0.0.0", "--host", "-h"),
|
|
19
|
-
port: int = typer.Option(8000, "--port", "-p"),
|
|
20
|
-
base_url: str = typer.Option("http://localhost:8000", "--base-url", "-b"),
|
|
21
|
-
app_mode: bool = typer.Option(False, "--app-mode", "-a", help="Use enhanced KalibrApp instead of basic Kalibr")
|
|
22
|
-
):
|
|
23
|
-
"""Serve a Kalibr-powered API locally."""
|
|
24
|
-
|
|
25
|
-
# Resolve the file path to an absolute path
|
|
12
|
+
def _load_user_module(file: str):
|
|
26
13
|
file_path = Path(file).resolve()
|
|
27
|
-
# Check if the specified file exists
|
|
28
14
|
if not file_path.exists():
|
|
29
15
|
print(f"❌ Error: {file} not found")
|
|
30
16
|
raise typer.Exit(1)
|
|
31
|
-
|
|
32
|
-
# Create a module spec from the file location
|
|
33
17
|
spec = importlib.util.spec_from_file_location("user_app", file_path)
|
|
34
18
|
if not spec or not spec.loader:
|
|
35
19
|
print(f"❌ Error: Could not load {file}")
|
|
36
20
|
raise typer.Exit(1)
|
|
37
|
-
|
|
38
|
-
# Create a new module from the spec
|
|
39
21
|
module = importlib.util.module_from_spec(spec)
|
|
40
|
-
# Add the module to sys.modules so it can be imported
|
|
41
22
|
sys.modules["user_app"] = module
|
|
42
|
-
|
|
43
23
|
try:
|
|
44
|
-
# Execute the module's code
|
|
45
24
|
spec.loader.exec_module(module)
|
|
46
25
|
except Exception as e:
|
|
47
26
|
print(f"❌ Error loading {file}: {e}")
|
|
48
27
|
raise typer.Exit(1)
|
|
28
|
+
return module
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
def serve(
|
|
32
|
+
file: str = typer.Argument("kalibr_app.py", help="Python file with Kalibr app"),
|
|
33
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h"),
|
|
34
|
+
port: int = typer.Option(8000, "--port", "-p"),
|
|
35
|
+
base_url: str = typer.Option("http://localhost:8000", "--base-url", "-b"),
|
|
36
|
+
):
|
|
37
|
+
"""Serve a Kalibr-powered API locally."""
|
|
38
|
+
module = _load_user_module(file)
|
|
49
39
|
|
|
50
40
|
# Import Kalibr classes
|
|
51
41
|
from kalibr import Kalibr, KalibrApp
|
|
52
42
|
kalibr_instance = None
|
|
53
|
-
|
|
54
|
-
# Iterate through the attributes of the loaded module
|
|
55
43
|
for attr_name in dir(module):
|
|
56
44
|
attr = getattr(module, attr_name)
|
|
57
|
-
# Check if the attribute is an instance of Kalibr (or KalibrApp when implemented)
|
|
58
45
|
if isinstance(attr, Kalibr) or (KalibrApp and isinstance(attr, KalibrApp)):
|
|
59
46
|
kalibr_instance = attr
|
|
60
|
-
# Override the base_url of the Kalibr instance if the --base-url flag was provided
|
|
61
47
|
kalibr_instance.base_url = base_url
|
|
62
48
|
break
|
|
63
|
-
|
|
64
|
-
# If no Kalibr instance was found, raise an error
|
|
65
49
|
if not kalibr_instance:
|
|
66
50
|
print(f"❌ Error: No Kalibr/KalibrApp instance found in {file}")
|
|
67
51
|
raise typer.Exit(1)
|
|
68
52
|
|
|
69
|
-
# Get the FastAPI application from the Kalibr/KalibrApp instance
|
|
70
53
|
if hasattr(kalibr_instance, 'get_app'):
|
|
71
54
|
fastapi_app = kalibr_instance.get_app()
|
|
72
55
|
elif hasattr(kalibr_instance, 'app'):
|
|
@@ -75,121 +58,112 @@ def serve(
|
|
|
75
58
|
print(f"❌ Error: Kalibr instance has no get_app() method or app attribute")
|
|
76
59
|
raise typer.Exit(1)
|
|
77
60
|
|
|
78
|
-
|
|
79
|
-
is_enhanced = KalibrApp is not None and isinstance(kalibr_instance, KalibrApp)
|
|
61
|
+
is_enhanced = 'KalibrApp' in str(type(kalibr_instance))
|
|
80
62
|
print(f"🚀 Starting {'Enhanced ' if is_enhanced else ''}Kalibr server from {file}")
|
|
81
63
|
print(f"📍 GPT (OpenAPI): {base_url}/openapi.json")
|
|
82
64
|
print(f"📍 Claude (MCP): {base_url}/mcp.json")
|
|
83
|
-
|
|
84
65
|
if is_enhanced:
|
|
85
66
|
print(f"📍 Gemini: {base_url}/schemas/gemini")
|
|
86
|
-
print(f"📍 Copilot: {base_url}/schemas/copilot")
|
|
67
|
+
print(f"📍 Copilot: {base_url}/schemas/copilot")
|
|
87
68
|
print(f"📍 Supported Models: {base_url}/models/supported")
|
|
88
69
|
print(f"📍 Health Check: {base_url}/health")
|
|
89
|
-
|
|
90
70
|
print(f"📍 Swagger UI: {base_url}/docs")
|
|
91
71
|
print(f"🔌 Actions registered: {list(kalibr_instance.actions.keys())}")
|
|
92
|
-
|
|
93
|
-
if is_enhanced:
|
|
94
|
-
print(f"📁 File handlers: {list(kalibr_instance.file_handlers.keys())}")
|
|
95
|
-
print(f"🌊 Stream actions: {list(kalibr_instance.stream_actions.keys())}")
|
|
96
|
-
print(f"⚡ Workflows: {list(kalibr_instance.workflows.keys())}")
|
|
97
72
|
|
|
98
|
-
# Run the FastAPI application using uvicorn
|
|
99
73
|
uvicorn.run(fastapi_app, host=host, port=port)
|
|
100
74
|
|
|
75
|
+
@app.command()
|
|
76
|
+
def package(
|
|
77
|
+
app_dir: str = typer.Option(".", "--app-dir", "-d", help="Directory containing your app"),
|
|
78
|
+
output: str = typer.Option("kalibr_bundle.zip", "--output", "-o", help="Bundle file"),
|
|
79
|
+
models: str = typer.Option("mcp,gpt-actions,gemini,copilot", "--models", "-m", help="Comma-separated models supported")
|
|
80
|
+
):
|
|
81
|
+
"""Create a deployable MCP bundle (code + schemas + metadata)."""
|
|
82
|
+
from kalibr.packager import package_app
|
|
83
|
+
try:
|
|
84
|
+
models_supported = [x.strip() for x in models.split(",") if x.strip()]
|
|
85
|
+
# Version best-effort
|
|
86
|
+
try:
|
|
87
|
+
from kalibr import __version__ as kalibr_version
|
|
88
|
+
except Exception:
|
|
89
|
+
kalibr_version = "unknown"
|
|
90
|
+
bundle_path = package_app(app_dir=app_dir, output=output, models_supported=models_supported, kalibr_version=kalibr_version)
|
|
91
|
+
print(f"📦 Bundle created: {bundle_path}")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"❌ Packaging error: {e}")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
|
|
101
96
|
@app.command()
|
|
102
97
|
def deploy(
|
|
103
|
-
file: str = typer.Argument(..., help="Python file to deploy"),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
file: str = typer.Argument(..., help="Python file to serve/deploy (e.g., kalibr_app.py)"),
|
|
99
|
+
name: str = typer.Option("", "--name", "-n", help="App name (defaults to filename)"),
|
|
100
|
+
runtime: str = typer.Option("local", "--runtime", "-r", help="Runtime: local|fly|render"),
|
|
101
|
+
host: str = typer.Option("0.0.0.0", "--host"),
|
|
102
|
+
port: int = typer.Option(8000, "--port"),
|
|
103
|
+
base_url: str = typer.Option("http://localhost:8000", "--base-url"),
|
|
109
104
|
):
|
|
110
|
-
"""Deploy
|
|
111
|
-
|
|
112
|
-
from kalibr.deployment import deploy_app, DeploymentConfig
|
|
113
|
-
|
|
114
|
-
# Check if file exists
|
|
105
|
+
"""Deploy via runtime router (no hosting burden on Kalibr)."""
|
|
106
|
+
from kalibr.runtime_router import deploy as router_deploy
|
|
115
107
|
file_path = Path(file)
|
|
116
108
|
if not file_path.exists():
|
|
117
109
|
print(f"❌ Error: {file} not found")
|
|
118
110
|
raise typer.Exit(1)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if not app_name:
|
|
122
|
-
app_name = file_path.stem.replace('_', '-').replace('.', '-')
|
|
123
|
-
|
|
124
|
-
print(f"🚀 Deploying {file} as '{app_name}' to {platform}...")
|
|
125
|
-
|
|
126
|
-
# Load environment variables
|
|
127
|
-
env_vars = {}
|
|
128
|
-
if env_file and Path(env_file).exists():
|
|
129
|
-
with open(env_file) as f:
|
|
130
|
-
for line in f:
|
|
131
|
-
if '=' in line and not line.startswith('#'):
|
|
132
|
-
key, value = line.strip().split('=', 1)
|
|
133
|
-
env_vars[key] = value
|
|
134
|
-
|
|
135
|
-
# Create deployment config
|
|
136
|
-
config = DeploymentConfig(
|
|
137
|
-
app_name=app_name,
|
|
138
|
-
memory_mb=memory,
|
|
139
|
-
timeout_seconds=timeout,
|
|
140
|
-
environment_vars=env_vars
|
|
141
|
-
)
|
|
142
|
-
|
|
111
|
+
if not name:
|
|
112
|
+
name = file_path.stem.replace('_', '-').replace('.', '-')
|
|
143
113
|
try:
|
|
144
|
-
result =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
print(f"\n🔗 Test it now:")
|
|
153
|
-
print(f" curl {result['endpoints']['health']}")
|
|
154
|
-
|
|
155
|
-
print(f"\n🤖 Connect to AI models:")
|
|
156
|
-
print(f" GPT Actions: {result['endpoints']['openapi']}")
|
|
157
|
-
print(f" Claude MCP: {result['endpoints']['mcp']}")
|
|
158
|
-
|
|
114
|
+
result = router_deploy(runtime=runtime, app_name=name, app_file=str(file_path), host=host, port=port, base_url=base_url)
|
|
115
|
+
if result.get("status") in ("success", "started"):
|
|
116
|
+
print("🎉 Deploy OK")
|
|
117
|
+
eps = result.get("endpoints", {})
|
|
118
|
+
if eps:
|
|
119
|
+
print("📍 Endpoints:")
|
|
120
|
+
for k, v in eps.items():
|
|
121
|
+
print(f" - {k}: {v}")
|
|
159
122
|
else:
|
|
160
|
-
print(
|
|
161
|
-
if result.get('logs'):
|
|
162
|
-
print(f"\n📋 Logs:")
|
|
163
|
-
print(result['logs'])
|
|
164
|
-
raise typer.Exit(1)
|
|
165
|
-
|
|
123
|
+
print("⚠️ Deploy finished with unknown status:", result)
|
|
166
124
|
except Exception as e:
|
|
167
125
|
print(f"❌ Deployment error: {e}")
|
|
168
126
|
raise typer.Exit(1)
|
|
169
127
|
|
|
128
|
+
@app.command()
|
|
129
|
+
def validate(
|
|
130
|
+
url: str = typer.Option("http://localhost:8000/mcp.json", "--mcp-url", help="URL to MCP manifest"),
|
|
131
|
+
):
|
|
132
|
+
"""Validate MCP manifest against minimal JSON schema & version hint."""
|
|
133
|
+
from kalibr.validator import validate_mcp_manifest
|
|
134
|
+
try:
|
|
135
|
+
resp = requests.get(url, timeout=10)
|
|
136
|
+
resp.raise_for_status()
|
|
137
|
+
manifest = resp.json()
|
|
138
|
+
validate_mcp_manifest(manifest)
|
|
139
|
+
print("✅ MCP manifest looks structurally valid.")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print(f"❌ Validation failed: {e}")
|
|
142
|
+
raise typer.Exit(1)
|
|
143
|
+
|
|
144
|
+
@app.command()
|
|
145
|
+
def update_schemas():
|
|
146
|
+
"""Stub: instruct users to upgrade SDK and regenerate manifests."""
|
|
147
|
+
from kalibr.validator import update_schemas as _upd
|
|
148
|
+
_upd()
|
|
149
|
+
|
|
170
150
|
@app.command()
|
|
171
151
|
def status(
|
|
172
|
-
app_url: str = typer.Argument(..., help="URL of deployed app"),
|
|
152
|
+
app_url: str = typer.Argument(..., help="URL of deployed Kalibr app"),
|
|
173
153
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed information")
|
|
174
154
|
):
|
|
175
155
|
"""Check status of a deployed Kalibr app."""
|
|
176
|
-
|
|
177
156
|
try:
|
|
178
|
-
# Check health endpoint
|
|
179
157
|
health_url = f"{app_url.rstrip('/')}/health"
|
|
180
158
|
response = requests.get(health_url, timeout=10)
|
|
181
|
-
|
|
182
159
|
if response.status_code == 200:
|
|
183
160
|
health_data = response.json()
|
|
184
161
|
print(f"✅ App is healthy at {app_url}")
|
|
185
162
|
print(f" Version: {health_data.get('version', 'unknown')}")
|
|
186
163
|
print(f" Features: {health_data.get('features', {})}")
|
|
187
|
-
|
|
188
164
|
if verbose:
|
|
189
|
-
# Check available schemas
|
|
190
165
|
schemas = ["mcp.json", "openapi.json", "schemas/gemini", "schemas/copilot"]
|
|
191
166
|
print(f"\n📊 Available AI model schemas:")
|
|
192
|
-
|
|
193
167
|
for schema in schemas:
|
|
194
168
|
schema_url = f"{app_url.rstrip('/')}/{schema}"
|
|
195
169
|
try:
|
|
@@ -202,465 +176,22 @@ def status(
|
|
|
202
176
|
else:
|
|
203
177
|
print(f"❌ App at {app_url} is not responding (HTTP {response.status_code})")
|
|
204
178
|
raise typer.Exit(1)
|
|
205
|
-
|
|
206
179
|
except requests.exceptions.RequestException as e:
|
|
207
180
|
print(f"❌ Cannot connect to {app_url}: {e}")
|
|
208
181
|
raise typer.Exit(1)
|
|
209
182
|
|
|
210
|
-
@app.command()
|
|
211
|
-
def list_platforms():
|
|
212
|
-
"""List available deployment platforms and their requirements."""
|
|
213
|
-
|
|
214
|
-
print("🚀 Available Deployment Platforms:")
|
|
215
|
-
print()
|
|
216
|
-
|
|
217
|
-
platforms = [
|
|
218
|
-
{
|
|
219
|
-
"name": "Fly.io",
|
|
220
|
-
"command": "fly",
|
|
221
|
-
"requirements": [
|
|
222
|
-
"Install flyctl: https://fly.io/docs/flyctl/install/",
|
|
223
|
-
"Login: flyctl auth login",
|
|
224
|
-
"Create app: flyctl apps create your-app-name"
|
|
225
|
-
],
|
|
226
|
-
"example": "kalibr deploy my_app.py --platform fly --name my-api"
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
"name": "AWS Lambda",
|
|
230
|
-
"command": "aws-lambda",
|
|
231
|
-
"requirements": [
|
|
232
|
-
"Install AWS CLI: https://aws.amazon.com/cli/",
|
|
233
|
-
"Configure credentials: aws configure",
|
|
234
|
-
"Set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY"
|
|
235
|
-
],
|
|
236
|
-
"example": "kalibr deploy my_app.py --platform aws-lambda --name my-api"
|
|
237
|
-
}
|
|
238
|
-
]
|
|
239
|
-
|
|
240
|
-
for platform in platforms:
|
|
241
|
-
print(f"📦 {platform['name']}")
|
|
242
|
-
print(f" Command: --platform {platform['command']}")
|
|
243
|
-
print(" Setup Requirements:")
|
|
244
|
-
for req in platform['requirements']:
|
|
245
|
-
print(f" • {req}")
|
|
246
|
-
print(f" Example: {platform['example']}")
|
|
247
|
-
print()
|
|
248
|
-
|
|
249
|
-
print("💡 Tips:")
|
|
250
|
-
print(" • Use --env-file to load environment variables")
|
|
251
|
-
print(" • Check deployment status with: kalibr status <app-url>")
|
|
252
|
-
print(" • Test locally first with: kalibr serve my_app.py")
|
|
253
|
-
|
|
254
|
-
@app.command()
|
|
255
|
-
def setup(
|
|
256
|
-
platform: str = typer.Argument(..., help="Platform to setup (fly, aws)")
|
|
257
|
-
):
|
|
258
|
-
"""Setup deployment platform credentials and configuration."""
|
|
259
|
-
|
|
260
|
-
if platform == "fly":
|
|
261
|
-
print("🚀 Setting up Fly.io deployment...")
|
|
262
|
-
print()
|
|
263
|
-
print("📋 Required steps:")
|
|
264
|
-
print("1. Install flyctl:")
|
|
265
|
-
print(" • macOS: brew install flyctl")
|
|
266
|
-
print(" • Windows: iwr https://fly.io/install.ps1 -useb | iex")
|
|
267
|
-
print(" • Linux: curl -L https://fly.io/install.sh | sh")
|
|
268
|
-
print()
|
|
269
|
-
print("2. Create account and login:")
|
|
270
|
-
print(" flyctl auth signup")
|
|
271
|
-
print(" # or")
|
|
272
|
-
print(" flyctl auth login")
|
|
273
|
-
print()
|
|
274
|
-
print("3. Create your app:")
|
|
275
|
-
print(" flyctl apps create your-app-name")
|
|
276
|
-
print()
|
|
277
|
-
print("✅ After setup, deploy with:")
|
|
278
|
-
print(" kalibr deploy my_app.py --platform fly --name your-app-name")
|
|
279
|
-
|
|
280
|
-
elif platform == "aws":
|
|
281
|
-
print("☁️ Setting up AWS deployment...")
|
|
282
|
-
print()
|
|
283
|
-
print("📋 Required steps:")
|
|
284
|
-
print("1. Install AWS CLI:")
|
|
285
|
-
print(" • https://aws.amazon.com/cli/")
|
|
286
|
-
print()
|
|
287
|
-
print("2. Configure credentials:")
|
|
288
|
-
print(" aws configure")
|
|
289
|
-
print(" # Enter your Access Key ID and Secret Access Key")
|
|
290
|
-
print()
|
|
291
|
-
print("3. Set environment variables (alternative):")
|
|
292
|
-
print(" export AWS_ACCESS_KEY_ID=your_access_key")
|
|
293
|
-
print(" export AWS_SECRET_ACCESS_KEY=your_secret_key")
|
|
294
|
-
print(" export AWS_DEFAULT_REGION=us-east-1")
|
|
295
|
-
print()
|
|
296
|
-
print("✅ After setup, deploy with:")
|
|
297
|
-
print(" kalibr deploy my_app.py --platform aws-lambda --name your-app")
|
|
298
|
-
|
|
299
|
-
else:
|
|
300
|
-
print(f"❌ Unknown platform: {platform}")
|
|
301
|
-
print("Available platforms: fly, aws")
|
|
302
|
-
print("Run 'kalibr list-platforms' for more details")
|
|
303
|
-
raise typer.Exit(1)
|
|
304
|
-
|
|
305
|
-
@app.command()
|
|
306
|
-
def init(
|
|
307
|
-
template: str = typer.Option("basic", "--template", "-t", help="Template type (basic, enhanced, auth, analytics)"),
|
|
308
|
-
name: str = typer.Option("My API", "--name", "-n", help="API name")
|
|
309
|
-
):
|
|
310
|
-
"""Generate a starter Kalibr app with various templates."""
|
|
311
|
-
|
|
312
|
-
templates = {
|
|
313
|
-
"basic": {
|
|
314
|
-
"filename": "kalibr_app.py",
|
|
315
|
-
"content": f'''from kalibr import Kalibr
|
|
316
|
-
|
|
317
|
-
# Create your Kalibr API
|
|
318
|
-
app = Kalibr(title="{name}", base_url="http://localhost:8000")
|
|
319
|
-
|
|
320
|
-
@app.action("hello", "Say hello to someone")
|
|
321
|
-
def hello(name: str = "World"):
|
|
322
|
-
return {{"message": f"Hello, {{name}}!"}}
|
|
323
|
-
|
|
324
|
-
@app.action("add_contact", "Add a CRM contact")
|
|
325
|
-
def add_contact(name: str, email: str):
|
|
326
|
-
return {{"status": "success", "contact": {{"name": name, "email": email}}}}
|
|
327
|
-
|
|
328
|
-
@app.action("calculate", "Perform basic math operations")
|
|
329
|
-
def calculate(operation: str, a: float, b: float):
|
|
330
|
-
operations = {{
|
|
331
|
-
"add": a + b,
|
|
332
|
-
"subtract": a - b,
|
|
333
|
-
"multiply": a * b,
|
|
334
|
-
"divide": a / b if b != 0 else "Cannot divide by zero"
|
|
335
|
-
}}
|
|
336
|
-
return {{"result": operations.get(operation, "Invalid operation")}}
|
|
337
|
-
|
|
338
|
-
# Deploy with: kalibr deploy kalibr_app.py --platform fly --name my-api
|
|
339
|
-
''',
|
|
340
|
-
"description": "Basic Kalibr app with simple actions"
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
"enhanced": {
|
|
344
|
-
"filename": "enhanced_app.py",
|
|
345
|
-
"content": f'''from kalibr import KalibrApp
|
|
346
|
-
from kalibr.types import FileUpload, Session, StreamingResponse
|
|
347
|
-
import asyncio
|
|
348
|
-
|
|
349
|
-
# Create enhanced Kalibr app
|
|
350
|
-
app = KalibrApp(title="{name}", base_url="http://localhost:8000")
|
|
351
|
-
|
|
352
|
-
@app.action("hello", "Say hello with enhanced features")
|
|
353
|
-
def hello(name: str = "World"):
|
|
354
|
-
return {{"message": f"Hello, {{name}}!", "timestamp": "2024-01-01T00:00:00Z"}}
|
|
355
|
-
|
|
356
|
-
@app.file_handler("analyze_file", [".txt", ".json", ".csv"])
|
|
357
|
-
async def analyze_file(file: FileUpload):
|
|
358
|
-
# Analyze uploaded files
|
|
359
|
-
content = file.content.decode('utf-8')
|
|
360
|
-
return {{
|
|
361
|
-
"filename": file.filename,
|
|
362
|
-
"size": file.size,
|
|
363
|
-
"lines": len(content.split('\\n')),
|
|
364
|
-
"words": len(content.split()),
|
|
365
|
-
"upload_id": file.upload_id
|
|
366
|
-
}}
|
|
367
|
-
|
|
368
|
-
@app.session_action("save_preference", "Save user preference")
|
|
369
|
-
async def save_preference(session: Session, key: str, value: str):
|
|
370
|
-
session.set(key, value)
|
|
371
|
-
return {{"saved": {{key: value}}, "session_id": session.session_id}}
|
|
372
|
-
|
|
373
|
-
@app.stream_action("count_progress", "Stream counting progress")
|
|
374
|
-
async def count_progress(max_count: int = 10):
|
|
375
|
-
for i in range(max_count + 1):
|
|
376
|
-
yield {{
|
|
377
|
-
"count": i,
|
|
378
|
-
"progress": i / max_count * 100,
|
|
379
|
-
"message": f"Progress: {{i}}/{{max_count}}"
|
|
380
|
-
}}
|
|
381
|
-
await asyncio.sleep(0.5)
|
|
382
|
-
|
|
383
|
-
# Deploy with: kalibr deploy enhanced_app.py --platform fly --name my-enhanced-api
|
|
384
|
-
''',
|
|
385
|
-
"description": "Enhanced app with file uploads, sessions, and streaming"
|
|
386
|
-
},
|
|
387
|
-
|
|
388
|
-
"auth": {
|
|
389
|
-
"filename": "auth_app.py",
|
|
390
|
-
"content": f'''from kalibr import KalibrApp
|
|
391
|
-
from kalibr.auth_helpers import KalibrAuth, InMemoryUserStore, KalibrUser
|
|
392
|
-
from fastapi import Depends, HTTPException
|
|
393
|
-
import os
|
|
394
|
-
|
|
395
|
-
# Create app with authentication
|
|
396
|
-
app = KalibrApp(title="{name} (with Auth)", base_url="http://localhost:8000")
|
|
397
|
-
|
|
398
|
-
# Setup authentication
|
|
399
|
-
auth = KalibrAuth(secret_key=os.environ.get("SECRET_KEY", "your-secret-key-here"))
|
|
400
|
-
user_store = InMemoryUserStore()
|
|
401
|
-
|
|
402
|
-
# Create dependency for protected routes
|
|
403
|
-
async def get_current_user(token_payload = Depends(auth.create_auth_dependency(user_store.get_user))):
|
|
404
|
-
return token_payload
|
|
405
|
-
|
|
406
|
-
# Public endpoints
|
|
407
|
-
@app.action("public_hello", "Public greeting (no auth required)")
|
|
408
|
-
def public_hello(name: str = "World"):
|
|
409
|
-
return {{"message": f"Hello, {{name}}! This is a public endpoint."}}
|
|
410
|
-
|
|
411
|
-
@app.action("register", "Register a new user")
|
|
412
|
-
async def register(username: str, email: str, password: str):
|
|
413
|
-
try:
|
|
414
|
-
user = user_store.create_user(username, email, password)
|
|
415
|
-
token = auth.create_access_token({{"sub": user.id}})
|
|
416
|
-
return {{
|
|
417
|
-
"message": "User registered successfully",
|
|
418
|
-
"user": {{"id": user.id, "username": user.username}},
|
|
419
|
-
"token": token
|
|
420
|
-
}}
|
|
421
|
-
except ValueError as e:
|
|
422
|
-
raise HTTPException(400, str(e))
|
|
423
|
-
|
|
424
|
-
@app.action("login", "Login user and get token")
|
|
425
|
-
async def login(email: str, password: str):
|
|
426
|
-
user = user_store.get_user_by_email(email)
|
|
427
|
-
if not user or not user_store.verify_password(user.id, password):
|
|
428
|
-
raise HTTPException(401, "Invalid credentials")
|
|
429
|
-
|
|
430
|
-
token = auth.create_access_token({{"sub": user.id}})
|
|
431
|
-
return {{
|
|
432
|
-
"message": "Login successful",
|
|
433
|
-
"user": {{"id": user.id, "username": user.username}},
|
|
434
|
-
"token": token
|
|
435
|
-
}}
|
|
436
|
-
|
|
437
|
-
# Protected endpoints
|
|
438
|
-
@app.action("protected_hello", "Protected greeting (requires auth)")
|
|
439
|
-
async def protected_hello(name: str = "User", current_user: KalibrUser = Depends(get_current_user)):
|
|
440
|
-
return {{
|
|
441
|
-
"message": f"Hello, {{name}}! You are authenticated as {{current_user.username}}",
|
|
442
|
-
"user_id": current_user.id
|
|
443
|
-
}}
|
|
444
|
-
|
|
445
|
-
# Deploy with: kalibr deploy auth_app.py --platform fly --name my-auth-api --env-file .env
|
|
446
|
-
# Create .env file with: SECRET_KEY=your-actual-secret-key-here
|
|
447
|
-
''',
|
|
448
|
-
"description": "App with built-in user authentication and protected routes"
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
"analytics": {
|
|
452
|
-
"filename": "analytics_app.py",
|
|
453
|
-
"content": f'''from kalibr import KalibrApp
|
|
454
|
-
from kalibr.analytics import kalibr_analytics, MetricsCollector
|
|
455
|
-
from kalibr.types import FileUpload
|
|
456
|
-
|
|
457
|
-
# Create app with built-in analytics
|
|
458
|
-
@kalibr_analytics(storage="file", auto_track=True)
|
|
459
|
-
class AnalyticsApp(KalibrApp):
|
|
460
|
-
def __init__(self):
|
|
461
|
-
super().__init__(title="{name} (with Analytics)", base_url="http://localhost:8000")
|
|
462
|
-
|
|
463
|
-
app = AnalyticsApp()
|
|
464
|
-
|
|
465
|
-
@app.action("hello", "Say hello with automatic analytics tracking")
|
|
466
|
-
def hello(name: str = "World"):
|
|
467
|
-
# This call is automatically tracked for analytics
|
|
468
|
-
return {{"message": f"Hello, {{name}}!"}}
|
|
469
|
-
|
|
470
|
-
@app.action("process_data", "Process some data with custom analytics")
|
|
471
|
-
def process_data(data: str, process_type: str = "basic"):
|
|
472
|
-
# Record custom analytics event
|
|
473
|
-
app.record_custom_event("data_processing",
|
|
474
|
-
data_length=len(data),
|
|
475
|
-
process_type=process_type)
|
|
476
|
-
|
|
477
|
-
result = {{
|
|
478
|
-
"processed": True,
|
|
479
|
-
"input_length": len(data),
|
|
480
|
-
"output_length": len(data) * 2, # Simulated processing
|
|
481
|
-
"type": process_type
|
|
482
|
-
}}
|
|
483
|
-
return result
|
|
484
|
-
|
|
485
|
-
@app.action("get_analytics", "Get analytics for this app")
|
|
486
|
-
def get_analytics():
|
|
487
|
-
# Get built-in analytics
|
|
488
|
-
analytics = app.get_analytics()
|
|
489
|
-
return {{
|
|
490
|
-
"analytics": analytics,
|
|
491
|
-
"note": "Analytics are automatically tracked for all action calls"
|
|
492
|
-
}}
|
|
493
|
-
|
|
494
|
-
# File handler with analytics
|
|
495
|
-
@app.file_handler("upload_with_analytics", [".txt", ".json"])
|
|
496
|
-
async def upload_with_analytics(file: FileUpload):
|
|
497
|
-
# Custom analytics for file uploads
|
|
498
|
-
app.record_custom_event("file_upload",
|
|
499
|
-
filename=file.filename,
|
|
500
|
-
file_size=file.size,
|
|
501
|
-
file_type=file.content_type)
|
|
502
|
-
|
|
503
|
-
return {{
|
|
504
|
-
"uploaded": file.filename,
|
|
505
|
-
"size": file.size,
|
|
506
|
-
"analytics_recorded": True
|
|
507
|
-
}}
|
|
508
|
-
|
|
509
|
-
# Deploy with: kalibr deploy analytics_app.py --platform fly --name my-analytics-api
|
|
510
|
-
# Analytics data will be saved to kalibr_analytics.jsonl
|
|
511
|
-
''',
|
|
512
|
-
"description": "App with built-in analytics and metrics tracking"
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if template not in templates:
|
|
517
|
-
print(f"❌ Unknown template: {template}")
|
|
518
|
-
print(f"Available templates: {', '.join(templates.keys())}")
|
|
519
|
-
raise typer.Exit(1)
|
|
520
|
-
|
|
521
|
-
template_info = templates[template]
|
|
522
|
-
filename = template_info["filename"]
|
|
523
|
-
|
|
524
|
-
# Write the template file
|
|
525
|
-
with open(filename, "w") as f:
|
|
526
|
-
f.write(template_info["content"])
|
|
527
|
-
|
|
528
|
-
print(f"✅ Created {filename}")
|
|
529
|
-
print(f"📝 {template_info['description']}")
|
|
530
|
-
print()
|
|
531
|
-
print(f"🚀 Next steps:")
|
|
532
|
-
print(f" 1. Test locally: kalibr serve {filename}")
|
|
533
|
-
print(f" 2. Deploy: kalibr deploy {filename} --platform fly --name my-app")
|
|
534
|
-
print(f" 3. Check status: kalibr status https://my-app.fly.dev")
|
|
535
|
-
|
|
536
|
-
if template == "auth":
|
|
537
|
-
print(f"\n🔒 Don't forget to:")
|
|
538
|
-
print(f" • Create .env file with SECRET_KEY=your-secret-key")
|
|
539
|
-
print(f" • Use --env-file .env when deploying")
|
|
540
|
-
|
|
541
|
-
if template == "analytics":
|
|
542
|
-
print(f"\n📊 Analytics features:")
|
|
543
|
-
print(f" • Automatic tracking of all API calls")
|
|
544
|
-
print(f" • Custom event recording")
|
|
545
|
-
print(f" • Built-in metrics endpoint")
|
|
546
|
-
|
|
547
|
-
@app.command()
|
|
548
|
-
def test(
|
|
549
|
-
url: str = typer.Option("http://localhost:8000", "--url", "-u", help="Base URL of Kalibr server"),
|
|
550
|
-
action: str = typer.Option("", "--action", "-a", help="Specific action to test")
|
|
551
|
-
):
|
|
552
|
-
"""Test a running Kalibr server."""
|
|
553
|
-
import requests
|
|
554
|
-
import json
|
|
555
|
-
|
|
556
|
-
try:
|
|
557
|
-
# Test health endpoint
|
|
558
|
-
health_response = requests.get(f"{url}/health")
|
|
559
|
-
if health_response.status_code == 200:
|
|
560
|
-
health_data = health_response.json()
|
|
561
|
-
print("✅ Server is healthy!")
|
|
562
|
-
print(f"Version: {health_data.get('version', 'unknown')}")
|
|
563
|
-
print(f"Features: {health_data.get('features', {})}")
|
|
564
|
-
|
|
565
|
-
# Get available actions
|
|
566
|
-
root_response = requests.get(f"{url}/")
|
|
567
|
-
if root_response.status_code == 200:
|
|
568
|
-
root_data = root_response.json()
|
|
569
|
-
actions = root_data.get("actions", [])
|
|
570
|
-
print(f"📋 Available actions: {actions}")
|
|
571
|
-
|
|
572
|
-
if action and action in actions:
|
|
573
|
-
# Test specific action
|
|
574
|
-
test_data = {"test": "data"}
|
|
575
|
-
action_response = requests.post(f"{url}/proxy/{action}", json=test_data)
|
|
576
|
-
print(f"🧪 Testing {action}: {action_response.status_code}")
|
|
577
|
-
print(f"Response: {action_response.json()}")
|
|
578
|
-
|
|
579
|
-
# Test schema endpoints
|
|
580
|
-
schemas = ["mcp", "openapi", "gemini", "copilot"]
|
|
581
|
-
print("\n📊 Schema endpoints:")
|
|
582
|
-
for schema_type in schemas:
|
|
583
|
-
schema_url = f"{url}/schemas/{schema_type}" if schema_type in ["gemini", "copilot"] else f"{url}/{schema_type}.json"
|
|
584
|
-
try:
|
|
585
|
-
schema_response = requests.get(schema_url)
|
|
586
|
-
status = "✅" if schema_response.status_code == 200 else "❌"
|
|
587
|
-
print(f"{status} {schema_type}: {schema_url}")
|
|
588
|
-
except:
|
|
589
|
-
print(f"❌ {schema_type}: {schema_url}")
|
|
590
|
-
|
|
591
|
-
except requests.ConnectionError:
|
|
592
|
-
print(f"❌ Could not connect to {url}")
|
|
593
|
-
print("Make sure the Kalibr server is running")
|
|
594
|
-
|
|
595
|
-
@app.command()
|
|
596
|
-
def examples():
|
|
597
|
-
"""Copy example files to current directory."""
|
|
598
|
-
import shutil
|
|
599
|
-
from pathlib import Path
|
|
600
|
-
import sys
|
|
601
|
-
import kalibr
|
|
602
|
-
|
|
603
|
-
# Find examples directory - check multiple possible locations
|
|
604
|
-
kalibr_path = Path(kalibr.__file__).parent
|
|
605
|
-
|
|
606
|
-
# Location 1: Sibling to kalibr package (development install)
|
|
607
|
-
examples_src = kalibr_path.parent / "examples"
|
|
608
|
-
|
|
609
|
-
# Location 2: In site-packages parent (wheel install with data_files)
|
|
610
|
-
if not examples_src.exists():
|
|
611
|
-
site_packages = Path(kalibr.__file__).parent.parent
|
|
612
|
-
examples_src = site_packages.parent / "examples"
|
|
613
|
-
|
|
614
|
-
# Location 3: Check sys.prefix/examples
|
|
615
|
-
if not examples_src.exists():
|
|
616
|
-
examples_src = Path(sys.prefix) / "examples"
|
|
617
|
-
|
|
618
|
-
if not examples_src.exists():
|
|
619
|
-
print(f"❌ Examples directory not found.")
|
|
620
|
-
print(f" Checked locations:")
|
|
621
|
-
print(f" - {kalibr_path.parent / 'examples'}")
|
|
622
|
-
print(f" - {Path(kalibr.__file__).parent.parent.parent / 'examples'}")
|
|
623
|
-
print(f" - {Path(sys.prefix) / 'examples'}")
|
|
624
|
-
print("This might happen if kalibr was installed without examples.")
|
|
625
|
-
raise typer.Exit(1)
|
|
626
|
-
|
|
627
|
-
# Copy to current directory
|
|
628
|
-
examples_dest = Path.cwd() / "kalibr_examples"
|
|
629
|
-
|
|
630
|
-
if examples_dest.exists():
|
|
631
|
-
print(f"⚠️ Directory 'kalibr_examples' already exists")
|
|
632
|
-
overwrite = typer.confirm("Do you want to overwrite it?")
|
|
633
|
-
if not overwrite:
|
|
634
|
-
print("Cancelled.")
|
|
635
|
-
raise typer.Exit(0)
|
|
636
|
-
shutil.rmtree(examples_dest)
|
|
637
|
-
|
|
638
|
-
shutil.copytree(examples_src, examples_dest)
|
|
639
|
-
|
|
640
|
-
print(f"✅ Examples copied to: {examples_dest}")
|
|
641
|
-
print(f"\n📚 Available examples:")
|
|
642
|
-
for example in examples_dest.glob("*.py"):
|
|
643
|
-
print(f" - {example.name}")
|
|
644
|
-
|
|
645
|
-
print(f"\n🚀 Try running:")
|
|
646
|
-
print(f" kalibr-connect serve kalibr_examples/basic_kalibr_example.py")
|
|
647
|
-
print(f" kalibr-connect serve kalibr_examples/enhanced_kalibr_example.py")
|
|
648
|
-
|
|
649
183
|
@app.command()
|
|
650
184
|
def version():
|
|
651
|
-
"""Show Kalibr version information."""
|
|
652
185
|
try:
|
|
653
186
|
from kalibr import __version__
|
|
654
187
|
except (ImportError, AttributeError):
|
|
655
188
|
__version__ = "unknown"
|
|
656
|
-
|
|
657
189
|
print(f"Kalibr SDK version: {__version__}")
|
|
658
190
|
print("Enhanced multi-model AI integration framework")
|
|
659
191
|
print("Supports: GPT Actions, Claude MCP, Gemini Extensions, Copilot Plugins")
|
|
660
192
|
print("GitHub: https://github.com/devonakelley/kalibr-sdk")
|
|
661
193
|
|
|
662
194
|
def main():
|
|
663
|
-
# Load config file if it exists for token
|
|
664
195
|
config_file = Path.home() / ".kalibr" / "config.json"
|
|
665
196
|
if config_file.exists():
|
|
666
197
|
try:
|
|
@@ -668,10 +199,8 @@ def main():
|
|
|
668
199
|
if "api_token" in config and not os.environ.get("KALIBR_TOKEN"):
|
|
669
200
|
os.environ["KALIBR_TOKEN"] = config["api_token"]
|
|
670
201
|
except:
|
|
671
|
-
pass
|
|
672
|
-
|
|
673
|
-
# Run the Typer application
|
|
202
|
+
pass
|
|
674
203
|
app()
|
|
675
204
|
|
|
676
205
|
if __name__ == "__main__":
|
|
677
|
-
main()
|
|
206
|
+
main()
|