genxai-framework 0.1.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.
Files changed (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """GenXAI CLI - Command Line Interface for GenXAI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """CLI commands for GenXAI."""
2
+
3
+ from cli.commands.tool import tool
4
+ from cli.commands.metrics import metrics
5
+
6
+ __all__ = ["tool", "metrics"]
@@ -0,0 +1,85 @@
1
+ """Approval management CLI commands."""
2
+
3
+ import click
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+
7
+ from genxai.security.audit import get_approval_service
8
+
9
+ console = Console()
10
+
11
+
12
+ @click.group()
13
+ def approval():
14
+ """Manage approval requests."""
15
+ pass
16
+
17
+
18
+ @approval.command("list")
19
+ def list_approvals():
20
+ """List approval requests."""
21
+ approvals = get_approval_service()._requests.values()
22
+ if not approvals:
23
+ console.print("[yellow]No approval requests found.[/yellow]")
24
+ return
25
+
26
+ table = Table(title="Approval Requests")
27
+ table.add_column("Request ID", style="cyan")
28
+ table.add_column("Action", style="white")
29
+ table.add_column("Resource", style="green")
30
+ table.add_column("Actor", style="magenta")
31
+ table.add_column("Status", style="yellow")
32
+
33
+ for request in approvals:
34
+ table.add_row(
35
+ request.request_id,
36
+ request.action,
37
+ request.resource_id,
38
+ request.actor_id,
39
+ request.status,
40
+ )
41
+
42
+ console.print(table)
43
+
44
+
45
+ @approval.command("submit")
46
+ @click.option("--action", required=True)
47
+ @click.option("--resource", required=True)
48
+ @click.option("--actor", required=True)
49
+ def submit_approval(action: str, resource: str, actor: str):
50
+ """Submit a new approval request."""
51
+ request = get_approval_service().submit(action, resource, actor)
52
+ console.print(f"[green]✓ Approval submitted: {request.request_id}[/green]")
53
+
54
+
55
+ @approval.command("approve")
56
+ @click.argument("request_id")
57
+ def approve_request(request_id: str):
58
+ """Approve a request."""
59
+ request = get_approval_service().approve(request_id)
60
+ if not request:
61
+ console.print(f"[red]Request '{request_id}' not found.[/red]")
62
+ raise click.Abort()
63
+ console.print(f"[green]✓ Approved {request_id}[/green]")
64
+
65
+
66
+ @approval.command("reject")
67
+ @click.argument("request_id")
68
+ def reject_request(request_id: str):
69
+ """Reject a request."""
70
+ request = get_approval_service().reject(request_id)
71
+ if not request:
72
+ console.print(f"[red]Request '{request_id}' not found.[/red]")
73
+ raise click.Abort()
74
+ console.print(f"[yellow]✓ Rejected {request_id}[/yellow]")
75
+
76
+
77
+ @approval.command("clear")
78
+ def clear_requests():
79
+ """Clear all approval requests."""
80
+ get_approval_service().clear()
81
+ console.print("[green]✓ Cleared approval requests[/green]")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ approval()
cli/commands/audit.py ADDED
@@ -0,0 +1,127 @@
1
+ """Audit log management CLI commands."""
2
+
3
+ import csv
4
+ import json
5
+ from dataclasses import asdict
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from genxai.security.audit import get_audit_log
13
+
14
+ console = Console()
15
+
16
+
17
+ @click.group()
18
+ def audit():
19
+ """Manage audit logs."""
20
+ pass
21
+
22
+
23
+ @audit.command("list")
24
+ @click.option(
25
+ "--format",
26
+ "output_format",
27
+ type=click.Choice(["table", "json", "csv"]),
28
+ default="table",
29
+ )
30
+ def list_events(output_format: str):
31
+ """List audit events."""
32
+ events = get_audit_log().list_events()
33
+ if not events:
34
+ console.print("[yellow]No audit events found.[/yellow]")
35
+ return
36
+
37
+ if output_format == "json":
38
+ data = [asdict(event) for event in events]
39
+ click.echo(json.dumps(data, default=str, indent=2))
40
+ return
41
+
42
+ if output_format == "csv":
43
+ writer = csv.writer(click.get_text_stream("stdout"))
44
+ writer.writerow(["action", "actor_id", "resource_id", "status", "timestamp"])
45
+ for event in events:
46
+ writer.writerow(
47
+ [
48
+ event.action,
49
+ event.actor_id,
50
+ event.resource_id,
51
+ event.status,
52
+ event.timestamp.isoformat(),
53
+ ]
54
+ )
55
+ return
56
+
57
+ table = Table(title="Audit Events")
58
+ table.add_column("Action", style="cyan")
59
+ table.add_column("Actor", style="magenta")
60
+ table.add_column("Resource", style="green")
61
+ table.add_column("Status", style="yellow")
62
+ table.add_column("Timestamp", style="white")
63
+
64
+ for event in events:
65
+ table.add_row(
66
+ event.action,
67
+ event.actor_id,
68
+ event.resource_id,
69
+ event.status,
70
+ event.timestamp.isoformat(),
71
+ )
72
+
73
+ console.print(table)
74
+
75
+
76
+ @audit.command("export")
77
+ @click.option("--output", "output_path", required=True)
78
+ @click.option(
79
+ "--format",
80
+ "output_format",
81
+ type=click.Choice(["json", "csv"]),
82
+ default="json",
83
+ )
84
+ def export_events(output_path: str, output_format: str):
85
+ """Export audit events to a JSON/CSV file."""
86
+ events = get_audit_log().list_events()
87
+ export_path = Path(output_path)
88
+ if output_format == "csv":
89
+ if export_path.suffix.lower() != ".csv":
90
+ export_path = export_path.with_suffix(".csv")
91
+ with export_path.open("w", encoding="utf-8", newline="") as file:
92
+ writer = csv.writer(file)
93
+ writer.writerow(["action", "actor_id", "resource_id", "status", "timestamp"])
94
+ for event in events:
95
+ writer.writerow(
96
+ [
97
+ event.action,
98
+ event.actor_id,
99
+ event.resource_id,
100
+ event.status,
101
+ event.timestamp.isoformat(),
102
+ ]
103
+ )
104
+ else:
105
+ if export_path.suffix.lower() != ".json":
106
+ export_path = export_path.with_suffix(".json")
107
+ export_path.write_text(json.dumps([asdict(event) for event in events], default=str, indent=2))
108
+
109
+ console.print(f"[green]✓ Exported {len(events)} events to {export_path}[/green]")
110
+
111
+
112
+ @audit.command("clear")
113
+ def clear_events():
114
+ """Clear audit events."""
115
+ get_audit_log().clear()
116
+ console.print("[green]✓ Cleared audit log[/green]")
117
+
118
+
119
+ @audit.command("compact")
120
+ def compact_audit_db():
121
+ """Compact audit database (VACUUM)."""
122
+ get_audit_log()._store.vacuum()
123
+ console.print("[green]✓ Compacted audit database[/green]")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ audit()
@@ -0,0 +1,25 @@
1
+ """Metrics API CLI commands."""
2
+
3
+ import click
4
+
5
+
6
+ @click.group()
7
+ def metrics() -> None:
8
+ """Manage metrics API server."""
9
+ pass
10
+
11
+
12
+ @metrics.command("serve")
13
+ @click.option("--host", default="0.0.0.0", show_default=True, help="Host to bind")
14
+ @click.option("--port", default=8001, show_default=True, help="Port to bind")
15
+ @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload")
16
+ def serve(host: str, port: int, reload: bool) -> None:
17
+ """Start the metrics API server."""
18
+ try:
19
+ import uvicorn
20
+ except ImportError as exc:
21
+ raise click.ClickException(
22
+ "Uvicorn is required to serve the metrics API. Install with: pip install genxai[api]"
23
+ ) from exc
24
+
25
+ uvicorn.run("genxai.api.app:create_app", host=host, port=port, reload=reload, factory=True)
cli/commands/tool.py ADDED
@@ -0,0 +1,389 @@
1
+ """Tool management CLI commands."""
2
+
3
+ import click
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ from rich import print as rprint
10
+
11
+ from genxai.tools.persistence import ToolService
12
+ from genxai.tools.base import ToolCategory
13
+ from genxai.tools.registry import ToolRegistry
14
+ from genxai.tools.builtin import * # noqa: F403 - register built-in tools
15
+
16
+ console = Console()
17
+
18
+
19
+ @click.group()
20
+ def tool():
21
+ """Manage GenXAI tools."""
22
+ pass
23
+
24
+
25
+ @tool.command()
26
+ @click.option('--category', help='Filter by category')
27
+ @click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
28
+ def list(category: Optional[str], format: str):
29
+ """List all tools."""
30
+ try:
31
+ tools = ToolService.list_tools()
32
+
33
+ # Filter by category if specified
34
+ if category:
35
+ tools = [t for t in tools if t.category == category]
36
+
37
+ if not tools:
38
+ console.print("[yellow]No tools found.[/yellow]")
39
+ return
40
+
41
+ if format == 'json':
42
+ # JSON output
43
+ output = [t.to_dict() for t in tools]
44
+ click.echo(json.dumps(output, indent=2))
45
+ else:
46
+ # Table output
47
+ table = Table(title="GenXAI Tools")
48
+ table.add_column("Name", style="cyan")
49
+ table.add_column("Description", style="white")
50
+ table.add_column("Category", style="green")
51
+ table.add_column("Type", style="magenta")
52
+ table.add_column("Version", style="yellow")
53
+
54
+ for t in tools:
55
+ table.add_row(
56
+ t.name,
57
+ t.description[:50] + "..." if len(t.description) > 50 else t.description,
58
+ t.category,
59
+ t.tool_type,
60
+ t.version
61
+ )
62
+
63
+ console.print(table)
64
+ console.print(f"\n[bold]Total:[/bold] {len(tools)} tools")
65
+
66
+ except Exception as e:
67
+ console.print(f"[red]Error listing tools: {e}[/red]")
68
+ raise click.Abort()
69
+
70
+
71
+ @tool.command()
72
+ @click.argument('name')
73
+ def info(name: str):
74
+ """Show detailed information about a tool."""
75
+ try:
76
+ tool_model = ToolService.get_tool(name)
77
+
78
+ if not tool_model:
79
+ console.print(f"[red]Tool '{name}' not found.[/red]")
80
+ raise click.Abort()
81
+
82
+ # Display tool information
83
+ console.print(f"\n[bold cyan]Tool: {tool_model.name}[/bold cyan]")
84
+ console.print(f"[bold]Description:[/bold] {tool_model.description}")
85
+ console.print(f"[bold]Category:[/bold] {tool_model.category}")
86
+ console.print(f"[bold]Type:[/bold] {tool_model.tool_type}")
87
+ console.print(f"[bold]Version:[/bold] {tool_model.version}")
88
+ console.print(f"[bold]Author:[/bold] {tool_model.author}")
89
+ console.print(f"[bold]Tags:[/bold] {', '.join(tool_model.tags)}")
90
+ console.print(f"[bold]Created:[/bold] {tool_model.created_at}")
91
+ console.print(f"[bold]Updated:[/bold] {tool_model.updated_at}")
92
+
93
+ if tool_model.tool_type == "code_based":
94
+ console.print(f"\n[bold]Parameters:[/bold]")
95
+ for param in tool_model.parameters:
96
+ console.print(f" • {param['name']} ({param['type']}): {param['description']}")
97
+
98
+ console.print(f"\n[bold]Code:[/bold]")
99
+ console.print(f"[dim]{tool_model.code}[/dim]")
100
+
101
+ elif tool_model.tool_type == "template_based":
102
+ console.print(f"\n[bold]Template:[/bold] {tool_model.template_name}")
103
+ console.print(f"[bold]Configuration:[/bold]")
104
+ console.print(json.dumps(tool_model.template_config, indent=2))
105
+
106
+ except Exception as e:
107
+ console.print(f"[red]Error getting tool info: {e}[/red]")
108
+ raise click.Abort()
109
+
110
+
111
+ @tool.command()
112
+ @click.argument('query')
113
+ @click.option('--category', help='Filter by category')
114
+ def search(query: str, category: Optional[str]):
115
+ """Search tools by name, description, or tags."""
116
+ try:
117
+ tools = ToolService.list_tools()
118
+
119
+ # Filter by query
120
+ query_lower = query.lower()
121
+ results = [
122
+ t for t in tools
123
+ if query_lower in t.name.lower()
124
+ or query_lower in t.description.lower()
125
+ or any(query_lower in tag.lower() for tag in t.tags)
126
+ ]
127
+
128
+ # Filter by category if specified
129
+ if category:
130
+ results = [t for t in results if t.category == category]
131
+
132
+ if not results:
133
+ console.print(f"[yellow]No tools found matching '{query}'.[/yellow]")
134
+ return
135
+
136
+ # Display results
137
+ table = Table(title=f"Search Results for '{query}'")
138
+ table.add_column("Name", style="cyan")
139
+ table.add_column("Description", style="white")
140
+ table.add_column("Category", style="green")
141
+
142
+ for t in results:
143
+ table.add_row(
144
+ t.name,
145
+ t.description[:60] + "..." if len(t.description) > 60 else t.description,
146
+ t.category
147
+ )
148
+
149
+ console.print(table)
150
+ console.print(f"\n[bold]Found:[/bold] {len(results)} tools")
151
+
152
+ except Exception as e:
153
+ console.print(f"[red]Error searching tools: {e}[/red]")
154
+ raise click.Abort()
155
+
156
+
157
+ @tool.command()
158
+ @click.argument('name')
159
+ @click.option('--force', is_flag=True, help='Skip confirmation')
160
+ def delete(name: str, force: bool):
161
+ """Delete a tool."""
162
+ try:
163
+ tool_model = ToolService.get_tool(name)
164
+
165
+ if not tool_model:
166
+ console.print(f"[red]Tool '{name}' not found.[/red]")
167
+ raise click.Abort()
168
+
169
+ # Confirm deletion
170
+ if not force:
171
+ if not click.confirm(f"Are you sure you want to delete tool '{name}'?"):
172
+ console.print("[yellow]Deletion cancelled.[/yellow]")
173
+ return
174
+
175
+ # Delete tool
176
+ ToolService.delete_tool(name)
177
+ console.print(f"[green]✓ Tool '{name}' deleted successfully.[/green]")
178
+
179
+ except Exception as e:
180
+ console.print(f"[red]Error deleting tool: {e}[/red]")
181
+ raise click.Abort()
182
+
183
+
184
+ @tool.command()
185
+ @click.argument('name')
186
+ @click.option('--output', '-o', help='Output file path')
187
+ @click.option('--format', type=click.Choice(['json', 'py']), default='json', help='Export format')
188
+ def export(name: str, output: Optional[str], format: str):
189
+ """Export a tool to a file."""
190
+ try:
191
+ tool_model = ToolService.get_tool(name)
192
+
193
+ if not tool_model:
194
+ console.print(f"[red]Tool '{name}' not found.[/red]")
195
+ raise click.Abort()
196
+
197
+ # Determine output path
198
+ if not output:
199
+ output = f"{name}.{format}"
200
+
201
+ output_path = Path(output)
202
+
203
+ # Export based on format
204
+ if format == 'json':
205
+ data = tool_model.to_dict()
206
+ output_path.write_text(json.dumps(data, indent=2))
207
+ elif format == 'py':
208
+ if tool_model.tool_type != "code_based":
209
+ console.print("[red]Only code-based tools can be exported as Python files.[/red]")
210
+ raise click.Abort()
211
+
212
+ content = f'''"""
213
+ Tool: {tool_model.name}
214
+ Description: {tool_model.description}
215
+ Category: {tool_model.category}
216
+ """
217
+
218
+ {tool_model.code}
219
+ '''
220
+ output_path.write_text(content)
221
+
222
+ console.print(f"[green]✓ Tool exported to {output_path}[/green]")
223
+
224
+ except Exception as e:
225
+ console.print(f"[red]Error exporting tool: {e}[/red]")
226
+ raise click.Abort()
227
+
228
+
229
+ @tool.command("export-schema")
230
+ @click.option('--output', '-o', help='Output file path', default='tool_schemas.json')
231
+ @click.option('--category', help='Filter by category')
232
+ @click.option('--stdout', is_flag=True, help='Print schema bundle to stdout')
233
+ @click.option('--format', 'output_format', type=click.Choice(['json', 'yaml']), default='json', help='Output format')
234
+ def export_schema(output: str, category: Optional[str], stdout: bool, output_format: str):
235
+ """Export consolidated tool schema bundle to JSON."""
236
+ try:
237
+ category_filter = ToolCategory(category) if category else None
238
+ bundle = ToolRegistry.export_schema_bundle(category=category_filter)
239
+
240
+ if stdout:
241
+ if output_format == 'yaml':
242
+ try:
243
+ import yaml
244
+ except ImportError as exc:
245
+ raise ImportError(
246
+ "PyYAML is required for YAML output. Install with: pip install PyYAML"
247
+ ) from exc
248
+ click.echo(yaml.safe_dump(bundle, sort_keys=False))
249
+ else:
250
+ click.echo(json.dumps(bundle, indent=2))
251
+ return
252
+
253
+ if output_format == 'yaml' and not output.lower().endswith((".yaml", ".yml")):
254
+ output = f"{output}.yaml"
255
+
256
+ export_path = ToolRegistry.export_schema_bundle_to_file(
257
+ output,
258
+ category=category_filter,
259
+ )
260
+ console.print(
261
+ f"[green]✓ Tool schema bundle (v{ToolRegistry.SCHEMA_VERSION}) exported to {export_path}[/green]"
262
+ )
263
+ except ValueError:
264
+ console.print(f"[red]Invalid category: {category}[/red]")
265
+ console.print(f"Valid categories: {', '.join([c.value for c in ToolCategory])}")
266
+ raise click.Abort()
267
+ except Exception as e:
268
+ console.print(f"[red]Error exporting tool schemas: {e}[/red]")
269
+ raise click.Abort()
270
+
271
+
272
+ @tool.command()
273
+ @click.argument('file', type=click.Path(exists=True))
274
+ def import_tool(file: str):
275
+ """Import a tool from a file."""
276
+ try:
277
+ file_path = Path(file)
278
+
279
+ if file_path.suffix == '.json':
280
+ # Import from JSON
281
+ data = json.loads(file_path.read_text())
282
+
283
+ # Check if tool already exists
284
+ if ToolService.get_tool(data['name']):
285
+ console.print(f"[red]Tool '{data['name']}' already exists.[/red]")
286
+ raise click.Abort()
287
+
288
+ # Create tool
289
+ ToolService.save_tool(
290
+ name=data['name'],
291
+ description=data['description'],
292
+ category=data['category'],
293
+ tags=data.get('tags', []),
294
+ version=data.get('version', '1.0.0'),
295
+ author=data.get('author', 'GenXAI User'),
296
+ tool_type=data['tool_type'],
297
+ code=data.get('code'),
298
+ parameters=data.get('parameters'),
299
+ template_name=data.get('template_name'),
300
+ template_config=data.get('template_config'),
301
+ )
302
+
303
+ console.print(f"[green]✓ Tool '{data['name']}' imported successfully.[/green]")
304
+ else:
305
+ console.print("[red]Only JSON files are supported for import.[/red]")
306
+ raise click.Abort()
307
+
308
+ except Exception as e:
309
+ console.print(f"[red]Error importing tool: {e}[/red]")
310
+ raise click.Abort()
311
+
312
+
313
+ @tool.command()
314
+ @click.option('--name', required=True, help='Tool name')
315
+ @click.option('--description', required=True, help='Tool description')
316
+ @click.option('--category', required=True, help='Tool category')
317
+ @click.option('--template', help='Template name for template-based tools')
318
+ @click.option('--config', help='Template configuration (JSON string)')
319
+ @click.option('--code-file', type=click.Path(exists=True), help='Python file for code-based tools')
320
+ @click.option('--tags', help='Comma-separated tags')
321
+ def create(name: str, description: str, category: str, template: Optional[str],
322
+ config: Optional[str], code_file: Optional[str], tags: Optional[str]):
323
+ """Create a new tool."""
324
+ try:
325
+ # Check if tool already exists
326
+ if ToolService.get_tool(name):
327
+ console.print(f"[red]Tool '{name}' already exists.[/red]")
328
+ raise click.Abort()
329
+
330
+ # Validate category
331
+ try:
332
+ ToolCategory(category)
333
+ except ValueError:
334
+ console.print(f"[red]Invalid category: {category}[/red]")
335
+ console.print(f"Valid categories: {', '.join([c.value for c in ToolCategory])}")
336
+ raise click.Abort()
337
+
338
+ # Parse tags
339
+ tag_list = [t.strip() for t in tags.split(',')] if tags else []
340
+
341
+ # Create tool based on type
342
+ if template:
343
+ # Template-based tool
344
+ if not config:
345
+ console.print("[red]--config is required for template-based tools.[/red]")
346
+ raise click.Abort()
347
+
348
+ config_dict = json.loads(config)
349
+
350
+ ToolService.save_tool(
351
+ name=name,
352
+ description=description,
353
+ category=category,
354
+ tags=tag_list,
355
+ version="1.0.0",
356
+ author="GenXAI User",
357
+ tool_type="template_based",
358
+ template_name=template,
359
+ template_config=config_dict,
360
+ )
361
+
362
+ elif code_file:
363
+ # Code-based tool
364
+ code = Path(code_file).read_text()
365
+
366
+ ToolService.save_tool(
367
+ name=name,
368
+ description=description,
369
+ category=category,
370
+ tags=tag_list,
371
+ version="1.0.0",
372
+ author="GenXAI User",
373
+ tool_type="code_based",
374
+ code=code,
375
+ parameters=[], # TODO: Parse from code or require as input
376
+ )
377
+ else:
378
+ console.print("[red]Either --template or --code-file must be provided.[/red]")
379
+ raise click.Abort()
380
+
381
+ console.print(f"[green]✓ Tool '{name}' created successfully.[/green]")
382
+
383
+ except Exception as e:
384
+ console.print(f"[red]Error creating tool: {e}[/red]")
385
+ raise click.Abort()
386
+
387
+
388
+ if __name__ == '__main__':
389
+ tool()
cli/main.py ADDED
@@ -0,0 +1,32 @@
1
+ """GenXAI CLI - Main entry point."""
2
+
3
+ import click
4
+ from cli.commands import tool, metrics
5
+ from cli.commands.approval import approval
6
+ from cli.commands.audit import audit
7
+
8
+
9
+ @click.group()
10
+ @click.version_option(version='0.1.0', prog_name='genxai')
11
+ def cli():
12
+ """GenXAI - Multi-Agent AI Framework CLI.
13
+
14
+ Manage tools, agents, workflows, and more from the command line.
15
+ """
16
+ pass
17
+
18
+
19
+ # Register command groups
20
+ cli.add_command(tool)
21
+ cli.add_command(metrics)
22
+ cli.add_command(approval)
23
+ cli.add_command(audit)
24
+
25
+
26
+ def main():
27
+ """Main entry point for the CLI."""
28
+ cli()
29
+
30
+
31
+ if __name__ == '__main__':
32
+ main()