kalibr 1.0.22__py3-none-any.whl → 1.0.24__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 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
- @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
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
- # Print server information
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
- app_name: str = typer.Option("", "--name", "-n", help="App name (defaults to filename)"),
105
- platform: str = typer.Option("fly", "--platform", "-p", help="Deployment platform (fly, aws-lambda)"),
106
- memory: int = typer.Option(512, "--memory", help="Memory allocation in MB"),
107
- timeout: int = typer.Option(30, "--timeout", help="Timeout in seconds"),
108
- env_file: str = typer.Option("", "--env-file", help="Environment variables file")
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 a Kalibr app to your own cloud infrastructure."""
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
- # Generate app name from filename if not provided
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 = deploy_app(str(file_path), config, platform)
145
-
146
- if result["status"] == "success":
147
- print("🎉 Deployment successful!")
148
- print("\n📍 Your app is live at:")
149
- for name, url in result["endpoints"].items():
150
- print(f" {name.upper()}: {url}")
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(f" Deployment failed: {result.get('error', 'Unknown error')}")
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 # Ignore config file errors
672
-
673
- # Run the Typer application
202
+ pass
674
203
  app()
675
204
 
676
205
  if __name__ == "__main__":
677
- main()
206
+ main()