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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
cli/__init__.py
ADDED
cli/commands/__init__.py
ADDED
cli/commands/approval.py
ADDED
|
@@ -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()
|
cli/commands/metrics.py
ADDED
|
@@ -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()
|