ainative-python 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ainative/cli.py ADDED
@@ -0,0 +1,698 @@
1
+ """
2
+ AINative SDK Command Line Interface
3
+
4
+ Provides a comprehensive CLI for interacting with AINative Studio APIs.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import click
11
+ from typing import Optional, List, Dict, Any
12
+ import numpy as np
13
+ from datetime import datetime, timedelta
14
+
15
+ from . import AINativeClient, __version__
16
+ from .auth import AuthConfig
17
+ from .exceptions import AINativeException, APIError, AuthenticationError
18
+ from .zerodb.memory import MemoryPriority
19
+ from .agent_swarm import AgentType
20
+
21
+
22
+ # Global client instance
23
+ client: Optional[AINativeClient] = None
24
+
25
+
26
+ def get_client() -> AINativeClient:
27
+ """Get or create global client instance."""
28
+ global client
29
+ if client is None:
30
+ # Load configuration
31
+ api_key = os.getenv("AINATIVE_API_KEY")
32
+ api_secret = os.getenv("AINATIVE_API_SECRET")
33
+ base_url = os.getenv("AINATIVE_BASE_URL")
34
+ org_id = os.getenv("AINATIVE_ORG_ID")
35
+
36
+ if not api_key:
37
+ click.echo("Error: AINATIVE_API_KEY environment variable not set", err=True)
38
+ click.echo("Run: export AINATIVE_API_KEY=your-api-key", err=True)
39
+ sys.exit(1)
40
+
41
+ auth_config = AuthConfig(
42
+ api_key=api_key,
43
+ api_secret=api_secret
44
+ )
45
+
46
+ client = AINativeClient(
47
+ auth_config=auth_config,
48
+ base_url=base_url,
49
+ organization_id=org_id
50
+ )
51
+
52
+ return client
53
+
54
+
55
+ def handle_error(error: Exception):
56
+ """Handle and display errors consistently."""
57
+ if isinstance(error, AuthenticationError):
58
+ click.echo(f"Authentication Error: {error.message}", err=True)
59
+ click.echo("Check your API key and try again.", err=True)
60
+ elif isinstance(error, APIError):
61
+ click.echo(f"API Error ({error.status_code}): {error.message}", err=True)
62
+ if error.response_body:
63
+ try:
64
+ body = json.loads(error.response_body)
65
+ if body.get("detail"):
66
+ click.echo(f"Details: {body['detail']}", err=True)
67
+ except json.JSONDecodeError:
68
+ pass
69
+ elif isinstance(error, AINativeException):
70
+ click.echo(f"Error: {error.message}", err=True)
71
+ else:
72
+ click.echo(f"Unexpected error: {str(error)}", err=True)
73
+
74
+
75
+ def format_output(data: Any, format_type: str = "json") -> str:
76
+ """Format output data for display."""
77
+ if format_type == "json":
78
+ return json.dumps(data, indent=2, default=str)
79
+ elif format_type == "table":
80
+ # Simple table formatting for lists
81
+ if isinstance(data, list) and data:
82
+ if isinstance(data[0], dict):
83
+ headers = list(data[0].keys())
84
+ rows = []
85
+ for item in data:
86
+ row = [str(item.get(header, "")) for header in headers]
87
+ rows.append(row)
88
+
89
+ # Simple table display
90
+ result = "\t".join(headers) + "\n"
91
+ for row in rows:
92
+ result += "\t".join(row) + "\n"
93
+ return result
94
+ return json.dumps(data, indent=2, default=str)
95
+ else:
96
+ return str(data)
97
+
98
+
99
+ # Main CLI group
100
+ @click.group()
101
+ @click.version_option(version=__version__, prog_name="ainative")
102
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
103
+ @click.pass_context
104
+ def cli(ctx, verbose):
105
+ """AINative SDK Command Line Interface"""
106
+ ctx.ensure_object(dict)
107
+ ctx.obj["verbose"] = verbose
108
+
109
+
110
+ # Configuration commands
111
+ @cli.group()
112
+ def config():
113
+ """Configuration management commands"""
114
+ pass
115
+
116
+
117
+ @config.command()
118
+ @click.argument("key")
119
+ @click.argument("value")
120
+ def set(key, value):
121
+ """Set configuration value"""
122
+ if key == "api_key":
123
+ click.echo(f"Set AINATIVE_API_KEY environment variable to: {value}")
124
+ click.echo("Run: export AINATIVE_API_KEY=" + value)
125
+ elif key == "base_url":
126
+ click.echo(f"Set AINATIVE_BASE_URL environment variable to: {value}")
127
+ click.echo("Run: export AINATIVE_BASE_URL=" + value)
128
+ else:
129
+ click.echo(f"Unknown configuration key: {key}")
130
+
131
+
132
+ @config.command()
133
+ def show():
134
+ """Show current configuration"""
135
+ config_items = [
136
+ ("API Key", os.getenv("AINATIVE_API_KEY", "Not set")),
137
+ ("API Secret", "***" if os.getenv("AINATIVE_API_SECRET") else "Not set"),
138
+ ("Base URL", os.getenv("AINATIVE_BASE_URL", "Default")),
139
+ ("Organization ID", os.getenv("AINATIVE_ORG_ID", "Not set")),
140
+ ]
141
+
142
+ for key, value in config_items:
143
+ click.echo(f"{key}: {value}")
144
+
145
+
146
+ # Project commands
147
+ @cli.group()
148
+ def projects():
149
+ """ZeroDB project management commands"""
150
+ pass
151
+
152
+
153
+ @projects.command()
154
+ @click.option("--limit", default=10, help="Maximum number of projects to return")
155
+ @click.option("--offset", default=0, help="Number of projects to skip")
156
+ @click.option("--status", help="Filter by project status")
157
+ @click.option("--format", "output_format", default="json", type=click.Choice(["json", "table"]))
158
+ def list(limit, offset, status, output_format):
159
+ """List projects"""
160
+ try:
161
+ client = get_client()
162
+ from .zerodb.projects import ProjectStatus
163
+
164
+ status_filter = None
165
+ if status:
166
+ try:
167
+ status_filter = ProjectStatus(status)
168
+ except ValueError:
169
+ click.echo(f"Invalid status: {status}", err=True)
170
+ return
171
+
172
+ result = client.zerodb.projects.list(
173
+ limit=limit,
174
+ offset=offset,
175
+ status=status_filter
176
+ )
177
+
178
+ click.echo(format_output(result, output_format))
179
+
180
+ except Exception as e:
181
+ handle_error(e)
182
+
183
+
184
+ @projects.command()
185
+ @click.argument("name")
186
+ @click.option("--description", help="Project description")
187
+ @click.option("--metadata", help="Project metadata as JSON string")
188
+ def create(name, description, metadata):
189
+ """Create a new project"""
190
+ try:
191
+ client = get_client()
192
+
193
+ metadata_dict = {}
194
+ if metadata:
195
+ metadata_dict = json.loads(metadata)
196
+
197
+ result = client.zerodb.projects.create(
198
+ name=name,
199
+ description=description,
200
+ metadata=metadata_dict
201
+ )
202
+
203
+ click.echo(f"Created project: {result['id']}")
204
+ click.echo(format_output(result))
205
+
206
+ except Exception as e:
207
+ handle_error(e)
208
+
209
+
210
+ @projects.command()
211
+ @click.argument("project_id")
212
+ def get(project_id):
213
+ """Get project details"""
214
+ try:
215
+ client = get_client()
216
+ result = client.zerodb.projects.get(project_id)
217
+ click.echo(format_output(result))
218
+
219
+ except Exception as e:
220
+ handle_error(e)
221
+
222
+
223
+ @projects.command()
224
+ @click.argument("project_id")
225
+ @click.option("--reason", help="Reason for suspension")
226
+ def suspend(project_id, reason):
227
+ """Suspend a project"""
228
+ try:
229
+ client = get_client()
230
+ result = client.zerodb.projects.suspend(project_id, reason=reason)
231
+ click.echo(f"Project {project_id} suspended")
232
+ click.echo(format_output(result))
233
+
234
+ except Exception as e:
235
+ handle_error(e)
236
+
237
+
238
+ @projects.command()
239
+ @click.argument("project_id")
240
+ def activate(project_id):
241
+ """Activate a suspended project"""
242
+ try:
243
+ client = get_client()
244
+ result = client.zerodb.projects.activate(project_id)
245
+ click.echo(f"Project {project_id} activated")
246
+ click.echo(format_output(result))
247
+
248
+ except Exception as e:
249
+ handle_error(e)
250
+
251
+
252
+ @projects.command()
253
+ @click.argument("project_id")
254
+ @click.confirmation_option(prompt="Are you sure you want to delete this project?")
255
+ def delete(project_id):
256
+ """Delete a project"""
257
+ try:
258
+ client = get_client()
259
+ result = client.zerodb.projects.delete(project_id)
260
+ click.echo(f"Project {project_id} deleted")
261
+ click.echo(format_output(result))
262
+
263
+ except Exception as e:
264
+ handle_error(e)
265
+
266
+
267
+ # Vector commands
268
+ @cli.group()
269
+ def vectors():
270
+ """Vector operations commands"""
271
+ pass
272
+
273
+
274
+ @vectors.command()
275
+ @click.argument("project_id")
276
+ @click.argument("query", nargs=-1)
277
+ @click.option("--top-k", default=5, help="Number of results to return")
278
+ @click.option("--namespace", default="default", help="Vector namespace")
279
+ @click.option("--include-metadata", is_flag=True, default=True, help="Include metadata in results")
280
+ def search(project_id, query, top_k, namespace, include_metadata):
281
+ """Search vectors (requires vector as space-separated numbers)"""
282
+ try:
283
+ client = get_client()
284
+
285
+ if not query:
286
+ click.echo("Error: Query vector required", err=True)
287
+ click.echo("Example: ainative vectors search proj_123 0.1 0.2 0.3", err=True)
288
+ return
289
+
290
+ # Convert query to vector
291
+ try:
292
+ query_vector = [float(x) for x in query]
293
+ except ValueError:
294
+ click.echo("Error: Query must be numeric values", err=True)
295
+ return
296
+
297
+ results = client.zerodb.vectors.search(
298
+ project_id=project_id,
299
+ vector=query_vector,
300
+ top_k=top_k,
301
+ namespace=namespace,
302
+ include_metadata=include_metadata
303
+ )
304
+
305
+ click.echo(f"Found {len(results)} results:")
306
+ click.echo(format_output(results))
307
+
308
+ except Exception as e:
309
+ handle_error(e)
310
+
311
+
312
+ @vectors.command()
313
+ @click.argument("project_id")
314
+ @click.argument("vector_file", type=click.File("r"))
315
+ @click.option("--namespace", default="default", help="Vector namespace")
316
+ @click.option("--metadata-file", type=click.File("r"), help="JSON file containing metadata")
317
+ def upsert(project_id, vector_file, namespace, metadata_file):
318
+ """Upsert vectors from JSON file"""
319
+ try:
320
+ client = get_client()
321
+
322
+ # Load vectors
323
+ vectors_data = json.load(vector_file)
324
+
325
+ # Load metadata if provided
326
+ metadata = None
327
+ if metadata_file:
328
+ metadata = json.load(metadata_file)
329
+
330
+ result = client.zerodb.vectors.upsert(
331
+ project_id=project_id,
332
+ vectors=vectors_data,
333
+ metadata=metadata,
334
+ namespace=namespace
335
+ )
336
+
337
+ click.echo("Vectors upserted successfully")
338
+ click.echo(format_output(result))
339
+
340
+ except Exception as e:
341
+ handle_error(e)
342
+
343
+
344
+ @vectors.command()
345
+ @click.argument("project_id")
346
+ @click.option("--namespace", help="Vector namespace")
347
+ def stats(project_id, namespace):
348
+ """Get vector index statistics"""
349
+ try:
350
+ client = get_client()
351
+ result = client.zerodb.vectors.describe_index_stats(
352
+ project_id=project_id,
353
+ namespace=namespace
354
+ )
355
+
356
+ click.echo("Vector Index Statistics:")
357
+ click.echo(format_output(result))
358
+
359
+ except Exception as e:
360
+ handle_error(e)
361
+
362
+
363
+ # Memory commands
364
+ @cli.group()
365
+ def memory():
366
+ """Memory operations commands"""
367
+ pass
368
+
369
+
370
+ @memory.command()
371
+ @click.argument("content")
372
+ @click.option("--title", help="Memory title")
373
+ @click.option("--tags", help="Comma-separated tags")
374
+ @click.option("--priority", type=click.Choice(["low", "medium", "high", "critical"]), default="medium")
375
+ @click.option("--project-id", help="Project ID")
376
+ def create(content, title, tags, priority, project_id):
377
+ """Create a new memory entry"""
378
+ try:
379
+ client = get_client()
380
+
381
+ tag_list = []
382
+ if tags:
383
+ tag_list = [tag.strip() for tag in tags.split(",")]
384
+
385
+ priority_enum = MemoryPriority(priority)
386
+
387
+ result = client.zerodb.memory.create(
388
+ content=content,
389
+ title=title,
390
+ tags=tag_list,
391
+ priority=priority_enum,
392
+ project_id=project_id
393
+ )
394
+
395
+ click.echo(f"Created memory: {result['id']}")
396
+ click.echo(format_output(result))
397
+
398
+ except Exception as e:
399
+ handle_error(e)
400
+
401
+
402
+ @memory.command()
403
+ @click.argument("query")
404
+ @click.option("--limit", default=5, help="Number of results to return")
405
+ @click.option("--project-id", help="Project ID filter")
406
+ @click.option("--semantic", is_flag=True, default=True, help="Use semantic search")
407
+ def search(query, limit, project_id, semantic):
408
+ """Search memory entries"""
409
+ try:
410
+ client = get_client()
411
+
412
+ results = client.zerodb.memory.search(
413
+ query=query,
414
+ limit=limit,
415
+ project_id=project_id,
416
+ semantic=semantic
417
+ )
418
+
419
+ click.echo(f"Found {len(results)} memories:")
420
+ click.echo(format_output(results))
421
+
422
+ except Exception as e:
423
+ handle_error(e)
424
+
425
+
426
+ @memory.command()
427
+ @click.option("--limit", default=10, help="Number of memories to return")
428
+ @click.option("--project-id", help="Project ID filter")
429
+ @click.option("--tags", help="Comma-separated tags filter")
430
+ @click.option("--priority", type=click.Choice(["low", "medium", "high", "critical"]))
431
+ def list(limit, project_id, tags, priority):
432
+ """List memory entries"""
433
+ try:
434
+ client = get_client()
435
+
436
+ tag_list = None
437
+ if tags:
438
+ tag_list = [tag.strip() for tag in tags.split(",")]
439
+
440
+ priority_filter = None
441
+ if priority:
442
+ priority_filter = MemoryPriority(priority)
443
+
444
+ result = client.zerodb.memory.list(
445
+ limit=limit,
446
+ project_id=project_id,
447
+ tags=tag_list,
448
+ priority=priority_filter
449
+ )
450
+
451
+ click.echo(format_output(result))
452
+
453
+ except Exception as e:
454
+ handle_error(e)
455
+
456
+
457
+ # Agent Swarm commands
458
+ @cli.group()
459
+ def swarm():
460
+ """Agent swarm operations commands"""
461
+ pass
462
+
463
+
464
+ @swarm.command("agent-types")
465
+ def agent_types():
466
+ """List available agent types"""
467
+ try:
468
+ client = get_client()
469
+ result = client.agent_swarm.get_agent_types()
470
+
471
+ click.echo("Available Agent Types:")
472
+ click.echo(format_output(result))
473
+
474
+ except Exception as e:
475
+ handle_error(e)
476
+
477
+
478
+ @swarm.command()
479
+ @click.argument("project_id")
480
+ @click.argument("objective")
481
+ @click.argument("agents_file", type=click.File("r"))
482
+ @click.option("--config-file", type=click.File("r"), help="JSON file with swarm configuration")
483
+ def start(project_id, objective, agents_file, config_file):
484
+ """Start agent swarm with agents from JSON file"""
485
+ try:
486
+ client = get_client()
487
+
488
+ # Load agents configuration
489
+ agents = json.load(agents_file)
490
+
491
+ # Load swarm configuration if provided
492
+ config = {}
493
+ if config_file:
494
+ config = json.load(config_file)
495
+
496
+ result = client.agent_swarm.start_swarm(
497
+ project_id=project_id,
498
+ agents=agents,
499
+ objective=objective,
500
+ config=config
501
+ )
502
+
503
+ click.echo(f"Started swarm: {result['id']}")
504
+ click.echo(format_output(result))
505
+
506
+ except Exception as e:
507
+ handle_error(e)
508
+
509
+
510
+ @swarm.command()
511
+ @click.argument("swarm_id")
512
+ def status(swarm_id):
513
+ """Get swarm status"""
514
+ try:
515
+ client = get_client()
516
+ result = client.agent_swarm.get_status(swarm_id)
517
+
518
+ click.echo(f"Swarm Status: {result.get('status', 'unknown')}")
519
+ click.echo(format_output(result))
520
+
521
+ except Exception as e:
522
+ handle_error(e)
523
+
524
+
525
+ @swarm.command()
526
+ @click.argument("swarm_id")
527
+ @click.argument("task")
528
+ @click.option("--context", help="Task context as JSON string")
529
+ @click.option("--agents", help="Comma-separated list of specific agent IDs")
530
+ def orchestrate(swarm_id, task, context, agents):
531
+ """Orchestrate a task within the swarm"""
532
+ try:
533
+ client = get_client()
534
+
535
+ context_dict = {}
536
+ if context:
537
+ context_dict = json.loads(context)
538
+
539
+ agent_list = None
540
+ if agents:
541
+ agent_list = [agent.strip() for agent in agents.split(",")]
542
+
543
+ result = client.agent_swarm.orchestrate(
544
+ swarm_id=swarm_id,
545
+ task=task,
546
+ context=context_dict,
547
+ agents=agent_list
548
+ )
549
+
550
+ click.echo(f"Task orchestrated: {result.get('task_id', 'unknown')}")
551
+ click.echo(format_output(result))
552
+
553
+ except Exception as e:
554
+ handle_error(e)
555
+
556
+
557
+ @swarm.command()
558
+ @click.argument("swarm_id")
559
+ @click.confirmation_option(prompt="Are you sure you want to stop this swarm?")
560
+ def stop(swarm_id):
561
+ """Stop an agent swarm"""
562
+ try:
563
+ client = get_client()
564
+ result = client.agent_swarm.stop_swarm(swarm_id)
565
+
566
+ click.echo(f"Swarm {swarm_id} stopped")
567
+ click.echo(format_output(result))
568
+
569
+ except Exception as e:
570
+ handle_error(e)
571
+
572
+
573
+ # Analytics commands
574
+ @cli.group()
575
+ def analytics():
576
+ """Analytics and metrics commands"""
577
+ pass
578
+
579
+
580
+ @analytics.command()
581
+ @click.option("--project-id", help="Project ID filter")
582
+ @click.option("--days", default=30, help="Number of days to analyze")
583
+ @click.option("--granularity", default="daily", type=click.Choice(["hourly", "daily", "weekly", "monthly"]))
584
+ def usage(project_id, days, granularity):
585
+ """Get usage analytics"""
586
+ try:
587
+ client = get_client()
588
+
589
+ end_date = datetime.now()
590
+ start_date = end_date - timedelta(days=days)
591
+
592
+ result = client.zerodb.analytics.get_usage(
593
+ project_id=project_id,
594
+ start_date=start_date,
595
+ end_date=end_date,
596
+ granularity=granularity
597
+ )
598
+
599
+ click.echo(f"Usage Analytics ({days} days, {granularity}):")
600
+ click.echo(format_output(result))
601
+
602
+ except Exception as e:
603
+ handle_error(e)
604
+
605
+
606
+ @analytics.command()
607
+ @click.option("--project-id", help="Project ID filter")
608
+ def costs(project_id):
609
+ """Get cost analysis"""
610
+ try:
611
+ client = get_client()
612
+ result = client.zerodb.analytics.get_cost_analysis(project_id=project_id)
613
+
614
+ click.echo("Cost Analysis:")
615
+ click.echo(format_output(result))
616
+
617
+ except Exception as e:
618
+ handle_error(e)
619
+
620
+
621
+ @analytics.command()
622
+ @click.argument("metric", type=click.Choice(["vectors", "queries", "storage", "errors"]))
623
+ @click.option("--project-id", help="Project ID filter")
624
+ @click.option("--days", default=30, help="Number of days to analyze")
625
+ def trends(metric, project_id, days):
626
+ """Get trend data for specific metrics"""
627
+ try:
628
+ client = get_client()
629
+ result = client.zerodb.analytics.get_trends(
630
+ metric=metric,
631
+ project_id=project_id,
632
+ period=days
633
+ )
634
+
635
+ click.echo(f"{metric.title()} Trends ({days} days):")
636
+ click.echo(format_output(result))
637
+
638
+ except Exception as e:
639
+ handle_error(e)
640
+
641
+
642
+ # Health check command
643
+ @cli.command()
644
+ def health():
645
+ """Check API health status"""
646
+ try:
647
+ client = get_client()
648
+ result = client.health_check()
649
+
650
+ click.echo("API Health Status:")
651
+ click.echo(format_output(result))
652
+
653
+ except Exception as e:
654
+ handle_error(e)
655
+
656
+
657
+ # Register new command groups
658
+ try:
659
+ from .commands import (
660
+ agents_group,
661
+ swarm_group,
662
+ task_group,
663
+ coordination_group,
664
+ learning_group,
665
+ state_group,
666
+ local_group,
667
+ inspect_group,
668
+ sync_group
669
+ )
670
+ cli.add_command(agents_group)
671
+ cli.add_command(swarm_group)
672
+ cli.add_command(task_group)
673
+ cli.add_command(coordination_group)
674
+ cli.add_command(learning_group)
675
+ cli.add_command(state_group)
676
+ cli.add_command(local_group)
677
+ cli.add_command(inspect_group)
678
+ cli.add_command(sync_group)
679
+ except ImportError as e:
680
+ # CLI command groups not available
681
+ # This is OK for initial usage
682
+ pass
683
+
684
+
685
+ def main():
686
+ """Main CLI entry point"""
687
+ try:
688
+ cli()
689
+ except KeyboardInterrupt:
690
+ click.echo("\nOperation cancelled.", err=True)
691
+ sys.exit(1)
692
+ except Exception as e:
693
+ click.echo(f"Unexpected error: {str(e)}", err=True)
694
+ sys.exit(1)
695
+
696
+
697
+ if __name__ == "__main__":
698
+ main()
@@ -0,0 +1,13 @@
1
+ """
2
+ CLI Utilities
3
+
4
+ Shared utilities for CLI commands including diff and formatting.
5
+ """
6
+
7
+ from .diff import DatabaseDiff
8
+ from .formatters import DiffFormatter
9
+
10
+ __all__ = [
11
+ "DatabaseDiff",
12
+ "DiffFormatter",
13
+ ]