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/__init__.py +46 -0
- ainative/agent_coordination.py +249 -0
- ainative/agent_identity_system.py +1566 -0
- ainative/agent_learning.py +239 -0
- ainative/agent_orchestration.py +202 -0
- ainative/agent_state.py +231 -0
- ainative/agent_swarm/__init__.py +510 -0
- ainative/auth.py +113 -0
- ainative/cli.py +698 -0
- ainative/cli_utils/__init__.py +13 -0
- ainative/cli_utils/diff.py +292 -0
- ainative/cli_utils/formatters.py +227 -0
- ainative/client.py +272 -0
- ainative/commands/__init__.py +28 -0
- ainative/commands/agents.py +238 -0
- ainative/commands/coordination.py +108 -0
- ainative/commands/inspect.py +483 -0
- ainative/commands/learning.py +119 -0
- ainative/commands/local.py +544 -0
- ainative/commands/state.py +144 -0
- ainative/commands/swarm.py +184 -0
- ainative/commands/sync.py +157 -0
- ainative/commands/tasks.py +191 -0
- ainative/exceptions.py +87 -0
- ainative/zerodb/__init__.py +89 -0
- ainative/zerodb/analytics.py +232 -0
- ainative/zerodb/memory.py +260 -0
- ainative/zerodb/projects.py +224 -0
- ainative/zerodb/tables.py +362 -0
- ainative/zerodb/vectors.py +231 -0
- ainative_python-2.0.0.dist-info/METADATA +550 -0
- ainative_python-2.0.0.dist-info/RECORD +35 -0
- ainative_python-2.0.0.dist-info/WHEEL +5 -0
- ainative_python-2.0.0.dist-info/entry_points.txt +2 -0
- ainative_python-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ZeroDB Local Environment Inspection Commands
|
|
3
|
+
|
|
4
|
+
Commands for examining local ZeroDB environment state and service health.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import json
|
|
9
|
+
import socket
|
|
10
|
+
import requests
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from typing import Dict, Any, Optional, Tuple
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Service configuration
|
|
22
|
+
SERVICES = {
|
|
23
|
+
"postgresql": {
|
|
24
|
+
"name": "PostgreSQL",
|
|
25
|
+
"host": "localhost",
|
|
26
|
+
"port": 5432,
|
|
27
|
+
"health_check": "tcp"
|
|
28
|
+
},
|
|
29
|
+
"qdrant": {
|
|
30
|
+
"name": "Qdrant",
|
|
31
|
+
"host": "localhost",
|
|
32
|
+
"port": 6333,
|
|
33
|
+
"health_check": "http",
|
|
34
|
+
"health_url": "http://localhost:6333/collections"
|
|
35
|
+
},
|
|
36
|
+
"minio": {
|
|
37
|
+
"name": "MinIO",
|
|
38
|
+
"host": "localhost",
|
|
39
|
+
"port": 9000,
|
|
40
|
+
"health_check": "http",
|
|
41
|
+
"health_url": "http://localhost:9000/minio/health/live"
|
|
42
|
+
},
|
|
43
|
+
"redpanda": {
|
|
44
|
+
"name": "RedPanda",
|
|
45
|
+
"host": "localhost",
|
|
46
|
+
"port": 9092,
|
|
47
|
+
"health_check": "tcp"
|
|
48
|
+
},
|
|
49
|
+
"api": {
|
|
50
|
+
"name": "API Server",
|
|
51
|
+
"host": "localhost",
|
|
52
|
+
"port": 8000,
|
|
53
|
+
"health_check": "http",
|
|
54
|
+
"health_url": "http://localhost:8000/health"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def check_tcp_port(host: str, port: int, timeout: float = 2.0) -> Tuple[bool, Optional[str]]:
|
|
60
|
+
"""Check if a TCP port is open and accepting connections."""
|
|
61
|
+
try:
|
|
62
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
63
|
+
sock.settimeout(timeout)
|
|
64
|
+
result = sock.connect_ex((host, port))
|
|
65
|
+
sock.close()
|
|
66
|
+
|
|
67
|
+
if result == 0:
|
|
68
|
+
return True, None
|
|
69
|
+
else:
|
|
70
|
+
return False, f"Port {port} not accepting connections"
|
|
71
|
+
except socket.gaierror:
|
|
72
|
+
return False, f"Hostname {host} could not be resolved"
|
|
73
|
+
except socket.timeout:
|
|
74
|
+
return False, f"Connection to {host}:{port} timed out"
|
|
75
|
+
except Exception as e:
|
|
76
|
+
return False, f"Error: {str(e)}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check_http_endpoint(url: str, timeout: float = 2.0) -> Tuple[bool, Optional[str], Optional[Dict]]:
|
|
80
|
+
"""Check if an HTTP endpoint is responding."""
|
|
81
|
+
try:
|
|
82
|
+
response = requests.get(url, timeout=timeout)
|
|
83
|
+
if response.status_code < 400:
|
|
84
|
+
try:
|
|
85
|
+
data = response.json() if response.text else None
|
|
86
|
+
return True, None, data
|
|
87
|
+
except json.JSONDecodeError:
|
|
88
|
+
return True, None, None
|
|
89
|
+
else:
|
|
90
|
+
return False, f"HTTP {response.status_code}", None
|
|
91
|
+
except requests.exceptions.ConnectionError:
|
|
92
|
+
return False, "Connection refused", None
|
|
93
|
+
except requests.exceptions.Timeout:
|
|
94
|
+
return False, "Request timeout", None
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return False, str(e), None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_service_health(service_id: str) -> Dict[str, Any]:
|
|
100
|
+
"""Check health status of a service."""
|
|
101
|
+
service = SERVICES.get(service_id)
|
|
102
|
+
if not service:
|
|
103
|
+
return {
|
|
104
|
+
"service": service_id,
|
|
105
|
+
"status": "unknown",
|
|
106
|
+
"error": "Service configuration not found"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
result = {
|
|
110
|
+
"service": service["name"],
|
|
111
|
+
"host": service["host"],
|
|
112
|
+
"port": service["port"],
|
|
113
|
+
"status": "unknown",
|
|
114
|
+
"error": None,
|
|
115
|
+
"data": None
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if service["health_check"] == "tcp":
|
|
119
|
+
is_up, error = check_tcp_port(service["host"], service["port"])
|
|
120
|
+
result["status"] = "up" if is_up else "down"
|
|
121
|
+
result["error"] = error
|
|
122
|
+
elif service["health_check"] == "http":
|
|
123
|
+
is_up, error, data = check_http_endpoint(service["health_url"])
|
|
124
|
+
result["status"] = "up" if is_up else "down"
|
|
125
|
+
result["error"] = error
|
|
126
|
+
result["data"] = data
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def format_status_indicator(status: str) -> str:
|
|
132
|
+
"""Format status with colored indicator."""
|
|
133
|
+
if status == "up":
|
|
134
|
+
return "[green]✅ UP[/green]"
|
|
135
|
+
elif status == "down":
|
|
136
|
+
return "[red]❌ DOWN[/red]"
|
|
137
|
+
elif status == "warning":
|
|
138
|
+
return "[yellow]⚠️ WARNING[/yellow]"
|
|
139
|
+
else:
|
|
140
|
+
return "[dim]❓ UNKNOWN[/dim]"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@click.group(name="inspect")
|
|
144
|
+
def inspect_group():
|
|
145
|
+
"""Examine local ZeroDB environment state."""
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@inspect_group.command(name="config")
|
|
150
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
151
|
+
def inspect_config(output_json: bool):
|
|
152
|
+
"""Show current ZeroDB Local configuration."""
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
config = {
|
|
156
|
+
"environment": {
|
|
157
|
+
"AINATIVE_API_KEY": "***" if os.getenv("AINATIVE_API_KEY") else "Not set",
|
|
158
|
+
"AINATIVE_API_SECRET": "***" if os.getenv("AINATIVE_API_SECRET") else "Not set",
|
|
159
|
+
"AINATIVE_BASE_URL": os.getenv("AINATIVE_BASE_URL", "Default"),
|
|
160
|
+
"AINATIVE_ORG_ID": os.getenv("AINATIVE_ORG_ID", "Not set")
|
|
161
|
+
},
|
|
162
|
+
"services": {
|
|
163
|
+
"PostgreSQL": f"localhost:{SERVICES['postgresql']['port']}",
|
|
164
|
+
"Qdrant": f"localhost:{SERVICES['qdrant']['port']}",
|
|
165
|
+
"MinIO": f"localhost:{SERVICES['minio']['port']}",
|
|
166
|
+
"RedPanda": f"localhost:{SERVICES['redpanda']['port']}",
|
|
167
|
+
"API Server": f"localhost:{SERVICES['api']['port']}"
|
|
168
|
+
},
|
|
169
|
+
"paths": {
|
|
170
|
+
"config_dir": os.path.expanduser("~/.ainative"),
|
|
171
|
+
"data_dir": os.path.expanduser("~/.ainative/data"),
|
|
172
|
+
"logs_dir": os.path.expanduser("~/.ainative/logs")
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if output_json:
|
|
177
|
+
click.echo(json.dumps(config, indent=2))
|
|
178
|
+
else:
|
|
179
|
+
console.print(Panel("[bold cyan]🔍 ZeroDB Local Configuration[/bold cyan]"))
|
|
180
|
+
console.print()
|
|
181
|
+
|
|
182
|
+
# Environment
|
|
183
|
+
console.print("[bold]Environment Variables:[/bold]")
|
|
184
|
+
for key, value in config["environment"].items():
|
|
185
|
+
console.print(f" • {key}: {value}")
|
|
186
|
+
console.print()
|
|
187
|
+
|
|
188
|
+
# Services
|
|
189
|
+
console.print("[bold]Service Endpoints:[/bold]")
|
|
190
|
+
for service, endpoint in config["services"].items():
|
|
191
|
+
console.print(f" • {service}: {endpoint}")
|
|
192
|
+
console.print()
|
|
193
|
+
|
|
194
|
+
# Paths
|
|
195
|
+
console.print("[bold]File Paths:[/bold]")
|
|
196
|
+
for path_name, path_value in config["paths"].items():
|
|
197
|
+
console.print(f" • {path_name}: {path_value}")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@inspect_group.command(name="services")
|
|
201
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
202
|
+
def inspect_services(output_json: bool):
|
|
203
|
+
"""Show all service health status."""
|
|
204
|
+
results = {}
|
|
205
|
+
|
|
206
|
+
for service_id in SERVICES.keys():
|
|
207
|
+
health = check_service_health(service_id)
|
|
208
|
+
results[service_id] = health
|
|
209
|
+
|
|
210
|
+
if output_json:
|
|
211
|
+
click.echo(json.dumps(results, indent=2))
|
|
212
|
+
else:
|
|
213
|
+
console.print()
|
|
214
|
+
console.print(Panel("[bold cyan]🔍 ZeroDB Local Services[/bold cyan]"))
|
|
215
|
+
console.print()
|
|
216
|
+
|
|
217
|
+
table = Table(show_header=True, header_style="bold")
|
|
218
|
+
table.add_column("Service", style="cyan", width=15)
|
|
219
|
+
table.add_column("Status", width=15)
|
|
220
|
+
table.add_column("Endpoint", style="dim", width=20)
|
|
221
|
+
table.add_column("Details", width=30)
|
|
222
|
+
|
|
223
|
+
for service_id, health in results.items():
|
|
224
|
+
status_display = format_status_indicator(health["status"])
|
|
225
|
+
endpoint = f"{health['host']}:{health['port']}"
|
|
226
|
+
details = health.get("error", "Healthy") or "Healthy"
|
|
227
|
+
|
|
228
|
+
table.add_row(
|
|
229
|
+
health["service"],
|
|
230
|
+
status_display,
|
|
231
|
+
endpoint,
|
|
232
|
+
details
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
console.print(table)
|
|
236
|
+
console.print()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@inspect_group.command(name="db")
|
|
240
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
241
|
+
def inspect_db(output_json: bool):
|
|
242
|
+
"""Show PostgreSQL database statistics."""
|
|
243
|
+
# Check PostgreSQL health first
|
|
244
|
+
health = check_service_health("postgresql")
|
|
245
|
+
|
|
246
|
+
if health["status"] != "up":
|
|
247
|
+
if output_json:
|
|
248
|
+
click.echo(json.dumps({"error": "PostgreSQL is not running", "status": "down"}, indent=2))
|
|
249
|
+
else:
|
|
250
|
+
console.print(f"[red]❌ PostgreSQL is not running[/red]")
|
|
251
|
+
console.print(f"Details: {health.get('error', 'Unknown error')}")
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
# Try to get database stats via API
|
|
255
|
+
try:
|
|
256
|
+
api_health = check_service_health("api")
|
|
257
|
+
if api_health["status"] == "up":
|
|
258
|
+
# Try to get stats from API
|
|
259
|
+
response = requests.get("http://localhost:8000/v1/admin/db/stats", timeout=5)
|
|
260
|
+
if response.status_code == 200:
|
|
261
|
+
stats = response.json()
|
|
262
|
+
|
|
263
|
+
if output_json:
|
|
264
|
+
click.echo(json.dumps(stats, indent=2))
|
|
265
|
+
else:
|
|
266
|
+
console.print()
|
|
267
|
+
console.print(Panel("[bold cyan]🗄️ PostgreSQL Database Statistics[/bold cyan]"))
|
|
268
|
+
console.print()
|
|
269
|
+
|
|
270
|
+
table = Table(show_header=True, header_style="bold")
|
|
271
|
+
table.add_column("Metric", style="cyan", width=25)
|
|
272
|
+
table.add_column("Value", width=30)
|
|
273
|
+
|
|
274
|
+
for key, value in stats.items():
|
|
275
|
+
table.add_row(key.replace("_", " ").title(), str(value))
|
|
276
|
+
|
|
277
|
+
console.print(table)
|
|
278
|
+
console.print()
|
|
279
|
+
return
|
|
280
|
+
except Exception as e:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
# Fallback: Show basic connection info
|
|
284
|
+
db_info = {
|
|
285
|
+
"status": "up",
|
|
286
|
+
"host": health["host"],
|
|
287
|
+
"port": health["port"],
|
|
288
|
+
"connection": "Accepting connections",
|
|
289
|
+
"note": "Start API server for detailed statistics"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if output_json:
|
|
293
|
+
click.echo(json.dumps(db_info, indent=2))
|
|
294
|
+
else:
|
|
295
|
+
console.print()
|
|
296
|
+
console.print(Panel("[bold cyan]🗄️ PostgreSQL Database[/bold cyan]"))
|
|
297
|
+
console.print()
|
|
298
|
+
console.print(f"[green]✅[/green] Status: Running")
|
|
299
|
+
console.print(f"Endpoint: {db_info['host']}:{db_info['port']}")
|
|
300
|
+
console.print(f"Connection: {db_info['connection']}")
|
|
301
|
+
console.print()
|
|
302
|
+
console.print(f"[dim]💡 {db_info['note']}[/dim]")
|
|
303
|
+
console.print()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@inspect_group.command(name="vectors")
|
|
307
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
308
|
+
def inspect_vectors(output_json: bool):
|
|
309
|
+
"""Show vector index statistics from Qdrant."""
|
|
310
|
+
# Check Qdrant health
|
|
311
|
+
health = check_service_health("qdrant")
|
|
312
|
+
|
|
313
|
+
if health["status"] != "up":
|
|
314
|
+
if output_json:
|
|
315
|
+
click.echo(json.dumps({"error": "Qdrant is not running", "status": "down"}, indent=2))
|
|
316
|
+
else:
|
|
317
|
+
console.print(f"[red]❌ Qdrant is not running[/red]")
|
|
318
|
+
console.print(f"Details: {health.get('error', 'Unknown error')}")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Try to get collection stats
|
|
322
|
+
try:
|
|
323
|
+
response = requests.get("http://localhost:6333/collections", timeout=5)
|
|
324
|
+
if response.status_code == 200:
|
|
325
|
+
data = response.json()
|
|
326
|
+
collections = data.get("result", {}).get("collections", [])
|
|
327
|
+
|
|
328
|
+
stats = {
|
|
329
|
+
"status": "up",
|
|
330
|
+
"total_collections": len(collections),
|
|
331
|
+
"collections": []
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Get details for each collection
|
|
335
|
+
for collection_info in collections:
|
|
336
|
+
collection_name = collection_info.get("name")
|
|
337
|
+
try:
|
|
338
|
+
coll_response = requests.get(f"http://localhost:6333/collections/{collection_name}", timeout=5)
|
|
339
|
+
if coll_response.status_code == 200:
|
|
340
|
+
coll_data = coll_response.json().get("result", {})
|
|
341
|
+
stats["collections"].append({
|
|
342
|
+
"name": collection_name,
|
|
343
|
+
"vectors_count": coll_data.get("vectors_count", 0),
|
|
344
|
+
"points_count": coll_data.get("points_count", 0),
|
|
345
|
+
"indexed_vectors_count": coll_data.get("indexed_vectors_count", 0)
|
|
346
|
+
})
|
|
347
|
+
except:
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
if output_json:
|
|
351
|
+
click.echo(json.dumps(stats, indent=2))
|
|
352
|
+
else:
|
|
353
|
+
console.print()
|
|
354
|
+
console.print(Panel("[bold cyan]🔍 Qdrant Vector Index Statistics[/bold cyan]"))
|
|
355
|
+
console.print()
|
|
356
|
+
console.print(f"[green]✅[/green] Status: Running")
|
|
357
|
+
console.print(f"Total Collections: {stats['total_collections']}")
|
|
358
|
+
console.print()
|
|
359
|
+
|
|
360
|
+
if stats["collections"]:
|
|
361
|
+
table = Table(show_header=True, header_style="bold")
|
|
362
|
+
table.add_column("Collection", style="cyan")
|
|
363
|
+
table.add_column("Vectors", justify="right")
|
|
364
|
+
table.add_column("Points", justify="right")
|
|
365
|
+
table.add_column("Indexed", justify="right")
|
|
366
|
+
|
|
367
|
+
for coll in stats["collections"]:
|
|
368
|
+
table.add_row(
|
|
369
|
+
coll["name"],
|
|
370
|
+
str(coll["vectors_count"]),
|
|
371
|
+
str(coll["points_count"]),
|
|
372
|
+
str(coll["indexed_vectors_count"])
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
console.print(table)
|
|
376
|
+
else:
|
|
377
|
+
console.print("[dim]No collections found[/dim]")
|
|
378
|
+
console.print()
|
|
379
|
+
return
|
|
380
|
+
except Exception as e:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
# Fallback
|
|
384
|
+
vector_info = {
|
|
385
|
+
"status": "up",
|
|
386
|
+
"host": health["host"],
|
|
387
|
+
"port": health["port"],
|
|
388
|
+
"note": "Could not retrieve collection statistics"
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if output_json:
|
|
392
|
+
click.echo(json.dumps(vector_info, indent=2))
|
|
393
|
+
else:
|
|
394
|
+
console.print()
|
|
395
|
+
console.print(Panel("[bold cyan]🔍 Qdrant Vector Index[/bold cyan]"))
|
|
396
|
+
console.print()
|
|
397
|
+
console.print(f"[green]✅[/green] Status: Running")
|
|
398
|
+
console.print(f"Endpoint: {vector_info['host']}:{vector_info['port']}")
|
|
399
|
+
console.print()
|
|
400
|
+
console.print(f"[yellow]⚠️[/yellow] {vector_info['note']}")
|
|
401
|
+
console.print()
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@inspect_group.command(name="sync")
|
|
405
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
406
|
+
def inspect_sync(output_json: bool):
|
|
407
|
+
"""Show sync status and last sync time."""
|
|
408
|
+
import os
|
|
409
|
+
from pathlib import Path
|
|
410
|
+
|
|
411
|
+
# Check for sync state file
|
|
412
|
+
sync_state_path = Path.home() / ".ainative" / "sync_state.json"
|
|
413
|
+
|
|
414
|
+
if sync_state_path.exists():
|
|
415
|
+
try:
|
|
416
|
+
with open(sync_state_path, 'r') as f:
|
|
417
|
+
sync_state = json.load(f)
|
|
418
|
+
|
|
419
|
+
last_sync = sync_state.get("last_sync")
|
|
420
|
+
if last_sync:
|
|
421
|
+
last_sync_dt = datetime.fromisoformat(last_sync)
|
|
422
|
+
time_since = datetime.now() - last_sync_dt
|
|
423
|
+
|
|
424
|
+
if time_since < timedelta(minutes=5):
|
|
425
|
+
status = "synced"
|
|
426
|
+
status_text = "Recently synced"
|
|
427
|
+
elif time_since < timedelta(hours=1):
|
|
428
|
+
status = "warning"
|
|
429
|
+
status_text = "Sync recommended"
|
|
430
|
+
else:
|
|
431
|
+
status = "outdated"
|
|
432
|
+
status_text = "Sync needed"
|
|
433
|
+
else:
|
|
434
|
+
status = "never"
|
|
435
|
+
status_text = "Never synced"
|
|
436
|
+
last_sync_dt = None
|
|
437
|
+
except Exception as e:
|
|
438
|
+
status = "error"
|
|
439
|
+
status_text = f"Error reading sync state: {str(e)}"
|
|
440
|
+
last_sync_dt = None
|
|
441
|
+
sync_state = {}
|
|
442
|
+
else:
|
|
443
|
+
status = "never"
|
|
444
|
+
status_text = "Never synced"
|
|
445
|
+
last_sync_dt = None
|
|
446
|
+
sync_state = {}
|
|
447
|
+
|
|
448
|
+
result = {
|
|
449
|
+
"status": status,
|
|
450
|
+
"status_text": status_text,
|
|
451
|
+
"last_sync": str(last_sync_dt) if last_sync_dt else None,
|
|
452
|
+
"sync_state_file": str(sync_state_path),
|
|
453
|
+
"file_exists": sync_state_path.exists(),
|
|
454
|
+
"details": sync_state
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if output_json:
|
|
458
|
+
click.echo(json.dumps(result, indent=2))
|
|
459
|
+
else:
|
|
460
|
+
console.print()
|
|
461
|
+
console.print(Panel("[bold cyan]🔄 Sync Status[/bold cyan]"))
|
|
462
|
+
console.print()
|
|
463
|
+
|
|
464
|
+
if status == "synced":
|
|
465
|
+
console.print(f"[green]✅ {status_text}[/green]")
|
|
466
|
+
elif status == "warning":
|
|
467
|
+
console.print(f"[yellow]⚠️ {status_text}[/yellow]")
|
|
468
|
+
elif status in ["outdated", "never"]:
|
|
469
|
+
console.print(f"[red]❌ {status_text}[/red]")
|
|
470
|
+
else:
|
|
471
|
+
console.print(f"[dim]❓ {status_text}[/dim]")
|
|
472
|
+
|
|
473
|
+
if last_sync_dt:
|
|
474
|
+
console.print(f"Last Sync: {last_sync_dt.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
475
|
+
console.print(f"Time Since: {str(time_since).split('.')[0]}")
|
|
476
|
+
|
|
477
|
+
console.print(f"State File: {sync_state_path}")
|
|
478
|
+
console.print(f"File Exists: {'Yes' if result['file_exists'] else 'No'}")
|
|
479
|
+
console.print()
|
|
480
|
+
|
|
481
|
+
if status != "synced":
|
|
482
|
+
console.print("[dim]💡 Run 'ainative sync' to synchronize local and remote state[/dim]")
|
|
483
|
+
console.print()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Learning CLI Commands
|
|
3
|
+
|
|
4
|
+
Commands for agent learning and feedback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import json
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from ..client import AINativeClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_client() -> AINativeClient:
|
|
21
|
+
"""Get authenticated client."""
|
|
22
|
+
import os
|
|
23
|
+
from ..auth import AuthConfig
|
|
24
|
+
|
|
25
|
+
api_key = os.getenv("AINATIVE_API_KEY")
|
|
26
|
+
if not api_key:
|
|
27
|
+
raise click.ClickException("AINATIVE_API_KEY environment variable not set")
|
|
28
|
+
|
|
29
|
+
return AINativeClient(
|
|
30
|
+
auth_config=AuthConfig(api_key=api_key),
|
|
31
|
+
base_url=os.getenv("AINATIVE_BASE_URL"),
|
|
32
|
+
organization_id=os.getenv("AINATIVE_ORG_ID")
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@click.group(name="learn")
|
|
37
|
+
def learning_group():
|
|
38
|
+
"""Agent learning and feedback."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@learning_group.command(name="feedback")
|
|
43
|
+
@click.option("--agent-id", required=True, help="Agent ID")
|
|
44
|
+
@click.option("--interaction-id", required=True, help="Interaction ID")
|
|
45
|
+
@click.option("--rating", type=int, required=True, help="Rating (1-5)")
|
|
46
|
+
@click.option("--comments", help="Optional feedback comments")
|
|
47
|
+
def submit_feedback(agent_id: str, interaction_id: str, rating: int, comments: Optional[str]):
|
|
48
|
+
"""Submit feedback for an agent interaction."""
|
|
49
|
+
try:
|
|
50
|
+
if rating < 1 or rating > 5:
|
|
51
|
+
raise click.ClickException("Rating must be between 1 and 5")
|
|
52
|
+
|
|
53
|
+
client = get_client()
|
|
54
|
+
result = client.agent_learning.submit_feedback(
|
|
55
|
+
agent_id=agent_id,
|
|
56
|
+
interaction_id=interaction_id,
|
|
57
|
+
rating=rating,
|
|
58
|
+
comments=comments
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
console.print(Panel(
|
|
62
|
+
f"[green]✓[/green] Feedback submitted",
|
|
63
|
+
title="Success"
|
|
64
|
+
))
|
|
65
|
+
console.print(json.dumps(result, indent=2))
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@learning_group.command(name="metrics")
|
|
72
|
+
@click.option("--agent-id", required=True, help="Agent ID")
|
|
73
|
+
@click.option("--time-range", default="7d", help="Time range (1d, 7d, 30d, 90d)")
|
|
74
|
+
@click.option("--metrics", help="Specific metrics (comma-separated)")
|
|
75
|
+
def get_metrics(agent_id: str, time_range: str, metrics: Optional[str]):
|
|
76
|
+
"""Get agent performance metrics."""
|
|
77
|
+
try:
|
|
78
|
+
client = get_client()
|
|
79
|
+
metric_list = metrics.split(",") if metrics else None
|
|
80
|
+
|
|
81
|
+
result = client.agent_learning.get_performance_metrics(
|
|
82
|
+
agent_id=agent_id,
|
|
83
|
+
metric_types=metric_list,
|
|
84
|
+
time_range=time_range
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
console.print(Panel(
|
|
88
|
+
json.dumps(result, indent=2),
|
|
89
|
+
title=f"Performance Metrics - {agent_id}"
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@learning_group.command(name="compare")
|
|
97
|
+
@click.option("--agents", required=True, help="Agent IDs to compare (comma-separated)")
|
|
98
|
+
@click.option("--metrics", required=True, help="Metrics to compare (comma-separated)")
|
|
99
|
+
@click.option("--time-range", default="7d", help="Time range")
|
|
100
|
+
def compare_agents(agents: str, metrics: str, time_range: str):
|
|
101
|
+
"""Compare multiple agents."""
|
|
102
|
+
try:
|
|
103
|
+
client = get_client()
|
|
104
|
+
agent_list = [a.strip() for a in agents.split(",")]
|
|
105
|
+
metric_list = [m.strip() for m in metrics.split(",")]
|
|
106
|
+
|
|
107
|
+
result = client.agent_learning.compare_agents(
|
|
108
|
+
agent_ids=agent_list,
|
|
109
|
+
metrics=metric_list,
|
|
110
|
+
time_range=time_range
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
console.print(Panel(
|
|
114
|
+
json.dumps(result, indent=2),
|
|
115
|
+
title="Agent Comparison"
|
|
116
|
+
))
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
click.echo(f"Error: {str(e)}", err=True)
|