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