kalibr 1.0.17__py3-none-any.whl → 1.0.20__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
@@ -1,31 +1,677 @@
1
1
  import typer
2
- from kalibr.kalibr_app import serve_app, deploy_app
3
-
4
- cli = typer.Typer(help="Kalibr Connect CLI")
5
-
6
- @cli.command("serve")
7
- def serve(file: str):
8
- """Run a Kalibr app locally."""
9
- serve_app(file)
10
-
11
- @cli.command("deploy")
12
- def deploy(file: str):
13
- """Deploy a Kalibr app."""
14
- deploy_app(file)
15
-
16
- @cli.command("usage")
17
- def usage():
18
- """Show usage guide."""
19
- print("""
20
- Kalibr Connect Commands:
21
- kalibr-connect serve <file> Run a Kalibr app locally.
22
- kalibr-connect deploy <file> Deploy your Kalibr app.
23
- kalibr-connect usage Show this usage guide.
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, KalibrApp
52
+ kalibr_instance = None
53
+
54
+ # Iterate through the attributes of the loaded module
55
+ for attr_name in dir(module):
56
+ attr = getattr(module, attr_name)
57
+ # Check if the attribute is an instance of Kalibr (or KalibrApp when implemented)
58
+ if isinstance(attr, Kalibr) or (KalibrApp and isinstance(attr, KalibrApp)):
59
+ kalibr_instance = attr
60
+ # Override the base_url of the Kalibr instance if the --base-url flag was provided
61
+ kalibr_instance.base_url = base_url
62
+ break
63
+
64
+ # If no Kalibr instance was found, raise an error
65
+ if not kalibr_instance:
66
+ print(f"❌ Error: No Kalibr/KalibrApp instance found in {file}")
67
+ raise typer.Exit(1)
68
+
69
+ # Get the FastAPI application from the Kalibr/KalibrApp instance
70
+ if hasattr(kalibr_instance, 'get_app'):
71
+ fastapi_app = kalibr_instance.get_app()
72
+ elif hasattr(kalibr_instance, 'app'):
73
+ fastapi_app = kalibr_instance.app
74
+ else:
75
+ print(f"❌ Error: Kalibr instance has no get_app() method or app attribute")
76
+ raise typer.Exit(1)
77
+
78
+ # Print server information
79
+ is_enhanced = KalibrApp is not None and isinstance(kalibr_instance, KalibrApp)
80
+ print(f"🚀 Starting {'Enhanced ' if is_enhanced else ''}Kalibr server from {file}")
81
+ print(f"📍 GPT (OpenAPI): {base_url}/openapi.json")
82
+ print(f"📍 Claude (MCP): {base_url}/mcp.json")
83
+
84
+ if is_enhanced:
85
+ print(f"📍 Gemini: {base_url}/schemas/gemini")
86
+ print(f"📍 Copilot: {base_url}/schemas/copilot")
87
+ print(f"📍 Supported Models: {base_url}/models/supported")
88
+ print(f"📍 Health Check: {base_url}/health")
89
+
90
+ print(f"📍 Swagger UI: {base_url}/docs")
91
+ 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
+
98
+ # Run the FastAPI application using uvicorn
99
+ uvicorn.run(fastapi_app, host=host, port=port)
100
+
101
+ @app.command()
102
+ 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")
109
+ ):
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
115
+ file_path = Path(file)
116
+ if not file_path.exists():
117
+ print(f"❌ Error: {file} not found")
118
+ 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
+
143
+ 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
+
159
+ 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
+
166
+ except Exception as e:
167
+ print(f"❌ Deployment error: {e}")
168
+ raise typer.Exit(1)
169
+
170
+ @app.command()
171
+ def status(
172
+ app_url: str = typer.Argument(..., help="URL of deployed app"),
173
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed information")
174
+ ):
175
+ """Check status of a deployed Kalibr app."""
176
+
177
+ try:
178
+ # Check health endpoint
179
+ health_url = f"{app_url.rstrip('/')}/health"
180
+ response = requests.get(health_url, timeout=10)
181
+
182
+ if response.status_code == 200:
183
+ health_data = response.json()
184
+ print(f"✅ App is healthy at {app_url}")
185
+ print(f" Version: {health_data.get('version', 'unknown')}")
186
+ print(f" Features: {health_data.get('features', {})}")
187
+
188
+ if verbose:
189
+ # Check available schemas
190
+ schemas = ["mcp.json", "openapi.json", "schemas/gemini", "schemas/copilot"]
191
+ print(f"\n📊 Available AI model schemas:")
192
+
193
+ for schema in schemas:
194
+ schema_url = f"{app_url.rstrip('/')}/{schema}"
195
+ try:
196
+ schema_response = requests.get(schema_url, timeout=5)
197
+ status_emoji = "✅" if schema_response.status_code == 200 else "❌"
198
+ model_name = schema.replace(".json", "").replace("schemas/", "")
199
+ print(f" {status_emoji} {model_name.upper()}: {schema_url}")
200
+ except:
201
+ print(f" ❌ {schema}: Connection failed")
202
+ else:
203
+ print(f"❌ App at {app_url} is not responding (HTTP {response.status_code})")
204
+ raise typer.Exit(1)
205
+
206
+ except requests.exceptions.RequestException as e:
207
+ print(f"❌ Cannot connect to {app_url}: {e}")
208
+ raise typer.Exit(1)
209
+
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
+ @app.command()
650
+ def version():
651
+ """Show Kalibr version information."""
652
+ try:
653
+ from kalibr import __version__
654
+ except (ImportError, AttributeError):
655
+ __version__ = "unknown"
656
+
657
+ print(f"Kalibr SDK version: {__version__}")
658
+ print("Enhanced multi-model AI integration framework")
659
+ print("Supports: GPT Actions, Claude MCP, Gemini Extensions, Copilot Plugins")
660
+ print("GitHub: https://github.com/devonakelley/kalibr-sdk")
25
661
 
26
662
  def main():
27
- """Entry point for console_scripts."""
28
- cli()
663
+ # Load config file if it exists for token
664
+ config_file = Path.home() / ".kalibr" / "config.json"
665
+ if config_file.exists():
666
+ try:
667
+ config = json.loads(config_file.read_text())
668
+ if "api_token" in config and not os.environ.get("KALIBR_TOKEN"):
669
+ os.environ["KALIBR_TOKEN"] = config["api_token"]
670
+ except:
671
+ pass # Ignore config file errors
672
+
673
+ # Run the Typer application
674
+ app()
29
675
 
30
676
  if __name__ == "__main__":
31
- main()
677
+ main()