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,544 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ZeroDB Local Environment Management Commands
|
|
3
|
+
|
|
4
|
+
Commands for managing local Docker-based ZeroDB development environment.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
import click
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich import box
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Service ports for health checks
|
|
24
|
+
SERVICE_PORTS = {
|
|
25
|
+
"postgres": 5432,
|
|
26
|
+
"qdrant": 6333,
|
|
27
|
+
"minio": 9000,
|
|
28
|
+
"redpanda": 9092,
|
|
29
|
+
"embeddings": 8001,
|
|
30
|
+
"zerodb-api": 8000,
|
|
31
|
+
"dashboard": 3000,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Service URLs for health checks
|
|
35
|
+
SERVICE_HEALTH_URLS = {
|
|
36
|
+
"postgres": None, # Use pg_isready
|
|
37
|
+
"qdrant": "http://localhost:6333/healthz",
|
|
38
|
+
"minio": "http://localhost:9000/minio/health/live",
|
|
39
|
+
"redpanda": None, # Use rpk command
|
|
40
|
+
"embeddings": "http://localhost:8001/health",
|
|
41
|
+
"zerodb-api": "http://localhost:8000/health",
|
|
42
|
+
"dashboard": "http://localhost:3000",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def find_zerodb_local_path() -> Optional[Path]:
|
|
47
|
+
"""Auto-detect zerodb-local directory path."""
|
|
48
|
+
# Check current directory
|
|
49
|
+
cwd = Path.cwd()
|
|
50
|
+
if (cwd / "docker-compose.yml").exists() and (cwd / ".env.local.example").exists():
|
|
51
|
+
return cwd
|
|
52
|
+
|
|
53
|
+
# Check parent directories
|
|
54
|
+
for parent in cwd.parents:
|
|
55
|
+
zerodb_local = parent / "zerodb-local"
|
|
56
|
+
if zerodb_local.exists() and (zerodb_local / "docker-compose.yml").exists():
|
|
57
|
+
return zerodb_local
|
|
58
|
+
|
|
59
|
+
# Check common locations
|
|
60
|
+
common_paths = [
|
|
61
|
+
Path.home() / "core" / "zerodb-local",
|
|
62
|
+
Path("/Users/aideveloper/core/zerodb-local"),
|
|
63
|
+
Path("~/zerodb-local").expanduser(),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for path in common_paths:
|
|
67
|
+
if path.exists() and (path / "docker-compose.yml").exists():
|
|
68
|
+
return path
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def check_docker_running() -> bool:
|
|
74
|
+
"""Check if Docker daemon is running."""
|
|
75
|
+
try:
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
["docker", "info"],
|
|
78
|
+
stdout=subprocess.PIPE,
|
|
79
|
+
stderr=subprocess.PIPE,
|
|
80
|
+
timeout=5
|
|
81
|
+
)
|
|
82
|
+
return result.returncode == 0
|
|
83
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def check_port_listening(port: int) -> bool:
|
|
88
|
+
"""Check if a port is listening."""
|
|
89
|
+
try:
|
|
90
|
+
import socket
|
|
91
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
92
|
+
sock.settimeout(1)
|
|
93
|
+
result = sock.connect_ex(("localhost", port))
|
|
94
|
+
return result == 0
|
|
95
|
+
except Exception:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_service_health(service: str, url: Optional[str]) -> str:
|
|
100
|
+
"""Check service health via HTTP endpoint."""
|
|
101
|
+
if not url:
|
|
102
|
+
# Check port only
|
|
103
|
+
port = SERVICE_PORTS.get(service)
|
|
104
|
+
if port and check_port_listening(port):
|
|
105
|
+
return "healthy"
|
|
106
|
+
return "down"
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
import urllib.request
|
|
110
|
+
req = urllib.request.Request(url, method="GET")
|
|
111
|
+
with urllib.request.urlopen(req, timeout=3) as response:
|
|
112
|
+
if response.status == 200:
|
|
113
|
+
return "healthy"
|
|
114
|
+
return "unhealthy"
|
|
115
|
+
except Exception:
|
|
116
|
+
return "down"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@click.group(name="local")
|
|
120
|
+
def local_group():
|
|
121
|
+
"""Manage local ZeroDB Docker environment."""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@local_group.command(name="init")
|
|
126
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
127
|
+
@click.option("--force", is_flag=True, help="Overwrite existing .env.local file")
|
|
128
|
+
def init_environment(path: Optional[str], force: bool):
|
|
129
|
+
"""Initialize local Docker environment."""
|
|
130
|
+
try:
|
|
131
|
+
# Find zerodb-local path
|
|
132
|
+
if path:
|
|
133
|
+
zerodb_path = Path(path)
|
|
134
|
+
else:
|
|
135
|
+
zerodb_path = find_zerodb_local_path()
|
|
136
|
+
|
|
137
|
+
if not zerodb_path or not zerodb_path.exists():
|
|
138
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
139
|
+
console.print("\nSearched locations:")
|
|
140
|
+
console.print(" • Current directory")
|
|
141
|
+
console.print(" • Parent directories")
|
|
142
|
+
console.print(" • ~/core/zerodb-local")
|
|
143
|
+
console.print("\nUse --path to specify location manually")
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
console.print(f"[cyan]Found zerodb-local at:[/cyan] {zerodb_path}")
|
|
147
|
+
|
|
148
|
+
# Check Docker
|
|
149
|
+
if not check_docker_running():
|
|
150
|
+
console.print("[red]✗[/red] Docker is not running")
|
|
151
|
+
console.print("Please start Docker Desktop and try again")
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
console.print("[green]✓[/green] Docker is running")
|
|
155
|
+
|
|
156
|
+
# Check docker-compose.yml
|
|
157
|
+
compose_file = zerodb_path / "docker-compose.yml"
|
|
158
|
+
if not compose_file.exists():
|
|
159
|
+
console.print(f"[red]✗[/red] docker-compose.yml not found at {compose_file}")
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
|
|
162
|
+
console.print("[green]✓[/green] docker-compose.yml found")
|
|
163
|
+
|
|
164
|
+
# Create .env.local from example
|
|
165
|
+
env_file = zerodb_path / ".env.local"
|
|
166
|
+
example_file = zerodb_path / ".env.local.example"
|
|
167
|
+
|
|
168
|
+
if env_file.exists() and not force:
|
|
169
|
+
console.print(f"[yellow]![/yellow] .env.local already exists")
|
|
170
|
+
console.print("Use --force to overwrite")
|
|
171
|
+
else:
|
|
172
|
+
if not example_file.exists():
|
|
173
|
+
console.print(f"[red]✗[/red] .env.local.example not found")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
|
|
176
|
+
env_file.write_text(example_file.read_text())
|
|
177
|
+
console.print(f"[green]✓[/green] Created .env.local from template")
|
|
178
|
+
|
|
179
|
+
# Create data directories
|
|
180
|
+
data_dir = zerodb_path / "data"
|
|
181
|
+
data_dir.mkdir(exist_ok=True)
|
|
182
|
+
|
|
183
|
+
for subdir in ["postgres", "qdrant", "minio", "redpanda", "embeddings"]:
|
|
184
|
+
(data_dir / subdir).mkdir(exist_ok=True)
|
|
185
|
+
|
|
186
|
+
console.print("[green]✓[/green] Created data directories")
|
|
187
|
+
|
|
188
|
+
# Success message
|
|
189
|
+
console.print()
|
|
190
|
+
panel = Panel(
|
|
191
|
+
"[green]Environment initialized successfully![/green]\n\n"
|
|
192
|
+
"Next steps:\n"
|
|
193
|
+
" 1. Review and update .env.local if needed\n"
|
|
194
|
+
" 2. Run: ainative local up\n"
|
|
195
|
+
" 3. Check status: ainative local status",
|
|
196
|
+
title="Initialization Complete",
|
|
197
|
+
border_style="green"
|
|
198
|
+
)
|
|
199
|
+
console.print(panel)
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@local_group.command(name="up")
|
|
207
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
208
|
+
@click.option("--detach/--no-detach", "-d/-D", default=True, help="Run in background")
|
|
209
|
+
@click.option("--build", is_flag=True, help="Build images before starting")
|
|
210
|
+
def start_services(path: Optional[str], detach: bool, build: bool):
|
|
211
|
+
"""Start local Docker services."""
|
|
212
|
+
try:
|
|
213
|
+
# Find zerodb-local path
|
|
214
|
+
if path:
|
|
215
|
+
zerodb_path = Path(path)
|
|
216
|
+
else:
|
|
217
|
+
zerodb_path = find_zerodb_local_path()
|
|
218
|
+
|
|
219
|
+
if not zerodb_path:
|
|
220
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
221
|
+
console.print("Run: ainative local init")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
# Check Docker
|
|
225
|
+
if not check_docker_running():
|
|
226
|
+
console.print("[red]✗[/red] Docker is not running")
|
|
227
|
+
sys.exit(1)
|
|
228
|
+
|
|
229
|
+
# Build command
|
|
230
|
+
cmd = ["docker-compose"]
|
|
231
|
+
|
|
232
|
+
if build:
|
|
233
|
+
console.print("[cyan]Building images...[/cyan]")
|
|
234
|
+
build_result = subprocess.run(
|
|
235
|
+
["docker-compose", "build"],
|
|
236
|
+
cwd=zerodb_path,
|
|
237
|
+
capture_output=True,
|
|
238
|
+
text=True
|
|
239
|
+
)
|
|
240
|
+
if build_result.returncode != 0:
|
|
241
|
+
console.print(f"[red]✗[/red] Build failed:\n{build_result.stderr}")
|
|
242
|
+
sys.exit(1)
|
|
243
|
+
console.print("[green]✓[/green] Build complete")
|
|
244
|
+
|
|
245
|
+
# Start services
|
|
246
|
+
console.print("[cyan]Starting services...[/cyan]")
|
|
247
|
+
|
|
248
|
+
cmd.extend(["up"])
|
|
249
|
+
if detach:
|
|
250
|
+
cmd.append("-d")
|
|
251
|
+
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
cmd,
|
|
254
|
+
cwd=zerodb_path,
|
|
255
|
+
capture_output=detach,
|
|
256
|
+
text=True
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if result.returncode != 0:
|
|
260
|
+
console.print(f"[red]✗[/red] Failed to start services")
|
|
261
|
+
if result.stderr:
|
|
262
|
+
console.print(result.stderr)
|
|
263
|
+
sys.exit(1)
|
|
264
|
+
|
|
265
|
+
if detach:
|
|
266
|
+
console.print("[green]✓[/green] Services started in background")
|
|
267
|
+
console.print()
|
|
268
|
+
console.print("View logs: ainative local logs")
|
|
269
|
+
console.print("Check status: ainative local status")
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
273
|
+
sys.exit(1)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@local_group.command(name="down")
|
|
277
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
278
|
+
@click.option("--volumes", is_flag=True, help="Remove volumes (deletes data)")
|
|
279
|
+
@click.confirmation_option(
|
|
280
|
+
"--volumes",
|
|
281
|
+
prompt="This will delete all data. Are you sure?"
|
|
282
|
+
)
|
|
283
|
+
def stop_services(path: Optional[str], volumes: bool):
|
|
284
|
+
"""Stop local Docker services."""
|
|
285
|
+
try:
|
|
286
|
+
# Find zerodb-local path
|
|
287
|
+
if path:
|
|
288
|
+
zerodb_path = Path(path)
|
|
289
|
+
else:
|
|
290
|
+
zerodb_path = find_zerodb_local_path()
|
|
291
|
+
|
|
292
|
+
if not zerodb_path:
|
|
293
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
# Stop services
|
|
297
|
+
console.print("[cyan]Stopping services...[/cyan]")
|
|
298
|
+
|
|
299
|
+
cmd = ["docker-compose", "down"]
|
|
300
|
+
if volumes:
|
|
301
|
+
cmd.append("-v")
|
|
302
|
+
console.print("[yellow]![/yellow] Removing volumes (data will be deleted)")
|
|
303
|
+
|
|
304
|
+
result = subprocess.run(
|
|
305
|
+
cmd,
|
|
306
|
+
cwd=zerodb_path,
|
|
307
|
+
capture_output=True,
|
|
308
|
+
text=True
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if result.returncode != 0:
|
|
312
|
+
console.print(f"[red]✗[/red] Failed to stop services")
|
|
313
|
+
console.print(result.stderr)
|
|
314
|
+
sys.exit(1)
|
|
315
|
+
|
|
316
|
+
console.print("[green]✓[/green] Services stopped")
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
320
|
+
sys.exit(1)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@local_group.command(name="logs")
|
|
324
|
+
@click.argument("service", required=False)
|
|
325
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
326
|
+
@click.option("--follow", "-f", is_flag=True, help="Follow log output")
|
|
327
|
+
@click.option("--tail", "-n", type=int, default=100, help="Number of lines to show")
|
|
328
|
+
def view_logs(service: Optional[str], path: Optional[str], follow: bool, tail: int):
|
|
329
|
+
"""View service logs."""
|
|
330
|
+
try:
|
|
331
|
+
# Find zerodb-local path
|
|
332
|
+
if path:
|
|
333
|
+
zerodb_path = Path(path)
|
|
334
|
+
else:
|
|
335
|
+
zerodb_path = find_zerodb_local_path()
|
|
336
|
+
|
|
337
|
+
if not zerodb_path:
|
|
338
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
339
|
+
sys.exit(1)
|
|
340
|
+
|
|
341
|
+
# Build command
|
|
342
|
+
cmd = ["docker-compose", "logs", f"--tail={tail}"]
|
|
343
|
+
|
|
344
|
+
if follow:
|
|
345
|
+
cmd.append("-f")
|
|
346
|
+
|
|
347
|
+
if service:
|
|
348
|
+
cmd.append(service)
|
|
349
|
+
|
|
350
|
+
# Execute
|
|
351
|
+
subprocess.run(cmd, cwd=zerodb_path)
|
|
352
|
+
|
|
353
|
+
except KeyboardInterrupt:
|
|
354
|
+
console.print("\n[yellow]Stopped following logs[/yellow]")
|
|
355
|
+
except Exception as e:
|
|
356
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
357
|
+
sys.exit(1)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@local_group.command(name="status")
|
|
361
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
362
|
+
@click.option("--json-output", "--json", is_flag=True, help="Output as JSON")
|
|
363
|
+
def show_status(path: Optional[str], json_output: bool):
|
|
364
|
+
"""Show service health status."""
|
|
365
|
+
try:
|
|
366
|
+
# Find zerodb-local path
|
|
367
|
+
if path:
|
|
368
|
+
zerodb_path = Path(path)
|
|
369
|
+
else:
|
|
370
|
+
zerodb_path = find_zerodb_local_path()
|
|
371
|
+
|
|
372
|
+
if not zerodb_path:
|
|
373
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
374
|
+
sys.exit(1)
|
|
375
|
+
|
|
376
|
+
# Check Docker
|
|
377
|
+
if not check_docker_running():
|
|
378
|
+
console.print("[red]✗[/red] Docker is not running")
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
|
|
381
|
+
# Get running containers
|
|
382
|
+
result = subprocess.run(
|
|
383
|
+
["docker-compose", "ps", "--format", "json"],
|
|
384
|
+
cwd=zerodb_path,
|
|
385
|
+
capture_output=True,
|
|
386
|
+
text=True
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if result.returncode != 0:
|
|
390
|
+
console.print("[red]✗[/red] Failed to get service status")
|
|
391
|
+
sys.exit(1)
|
|
392
|
+
|
|
393
|
+
# Parse container status
|
|
394
|
+
containers = []
|
|
395
|
+
for line in result.stdout.strip().split("\n"):
|
|
396
|
+
if line:
|
|
397
|
+
try:
|
|
398
|
+
containers.append(json.loads(line))
|
|
399
|
+
except json.JSONDecodeError:
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
# Check health of each service
|
|
403
|
+
status_data = []
|
|
404
|
+
for service_name, port in SERVICE_PORTS.items():
|
|
405
|
+
# Find container
|
|
406
|
+
container = next((c for c in containers if service_name in c.get("Service", "")), None)
|
|
407
|
+
|
|
408
|
+
if not container:
|
|
409
|
+
status_data.append({
|
|
410
|
+
"service": service_name,
|
|
411
|
+
"status": "down",
|
|
412
|
+
"port": port,
|
|
413
|
+
"health": "down"
|
|
414
|
+
})
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
# Check health
|
|
418
|
+
container_status = container.get("State", "unknown")
|
|
419
|
+
health_url = SERVICE_HEALTH_URLS.get(service_name)
|
|
420
|
+
health = check_service_health(service_name, health_url)
|
|
421
|
+
|
|
422
|
+
status_data.append({
|
|
423
|
+
"service": service_name,
|
|
424
|
+
"status": container_status,
|
|
425
|
+
"port": port,
|
|
426
|
+
"health": health
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
# Output
|
|
430
|
+
if json_output:
|
|
431
|
+
click.echo(json.dumps(status_data, indent=2))
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
# Create table
|
|
435
|
+
table = Table(title="ZeroDB Local Services", box=box.ROUNDED)
|
|
436
|
+
table.add_column("Service", style="cyan", no_wrap=True)
|
|
437
|
+
table.add_column("Status", style="bold")
|
|
438
|
+
table.add_column("Port", justify="right", style="dim")
|
|
439
|
+
table.add_column("Health", style="bold")
|
|
440
|
+
|
|
441
|
+
for item in status_data:
|
|
442
|
+
# Status color
|
|
443
|
+
status = item["status"]
|
|
444
|
+
if status == "running":
|
|
445
|
+
status_display = "[green]●[/green] Running"
|
|
446
|
+
elif status == "down":
|
|
447
|
+
status_display = "[red]●[/red] Down"
|
|
448
|
+
else:
|
|
449
|
+
status_display = f"[yellow]●[/yellow] {status}"
|
|
450
|
+
|
|
451
|
+
# Health color
|
|
452
|
+
health = item["health"]
|
|
453
|
+
if health == "healthy":
|
|
454
|
+
health_display = "[green]✓ Healthy[/green]"
|
|
455
|
+
elif health == "unhealthy":
|
|
456
|
+
health_display = "[yellow]! Unhealthy[/yellow]"
|
|
457
|
+
else:
|
|
458
|
+
health_display = "[red]✗ Down[/red]"
|
|
459
|
+
|
|
460
|
+
table.add_row(
|
|
461
|
+
item["service"],
|
|
462
|
+
status_display,
|
|
463
|
+
str(item["port"]),
|
|
464
|
+
health_display
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
console.print(table)
|
|
468
|
+
|
|
469
|
+
# Overall status
|
|
470
|
+
all_healthy = all(item["health"] == "healthy" for item in status_data)
|
|
471
|
+
if all_healthy:
|
|
472
|
+
console.print("\n[green]✓ All services are healthy[/green]")
|
|
473
|
+
else:
|
|
474
|
+
console.print("\n[yellow]! Some services are not healthy[/yellow]")
|
|
475
|
+
console.print("Run: ainative local logs [service] to investigate")
|
|
476
|
+
|
|
477
|
+
except Exception as e:
|
|
478
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
479
|
+
sys.exit(1)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@local_group.command(name="reset")
|
|
483
|
+
@click.option("--path", type=click.Path(), help="Path to zerodb-local directory")
|
|
484
|
+
@click.confirmation_option(prompt="This will delete ALL local data. Are you sure?")
|
|
485
|
+
def reset_database(path: Optional[str]):
|
|
486
|
+
"""Reset local database (WARNING: deletes all data)."""
|
|
487
|
+
try:
|
|
488
|
+
# Find zerodb-local path
|
|
489
|
+
if path:
|
|
490
|
+
zerodb_path = Path(path)
|
|
491
|
+
else:
|
|
492
|
+
zerodb_path = find_zerodb_local_path()
|
|
493
|
+
|
|
494
|
+
if not zerodb_path:
|
|
495
|
+
console.print("[red]✗[/red] Could not find zerodb-local directory")
|
|
496
|
+
sys.exit(1)
|
|
497
|
+
|
|
498
|
+
console.print("[yellow]Resetting database...[/yellow]")
|
|
499
|
+
|
|
500
|
+
# Stop services
|
|
501
|
+
console.print("[cyan]1/3[/cyan] Stopping services...")
|
|
502
|
+
subprocess.run(
|
|
503
|
+
["docker-compose", "down"],
|
|
504
|
+
cwd=zerodb_path,
|
|
505
|
+
capture_output=True
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# Remove volumes
|
|
509
|
+
console.print("[cyan]2/3[/cyan] Removing volumes...")
|
|
510
|
+
subprocess.run(
|
|
511
|
+
["docker-compose", "down", "-v"],
|
|
512
|
+
cwd=zerodb_path,
|
|
513
|
+
capture_output=True
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Remove data directory
|
|
517
|
+
data_dir = zerodb_path / "data"
|
|
518
|
+
if data_dir.exists():
|
|
519
|
+
import shutil
|
|
520
|
+
shutil.rmtree(data_dir)
|
|
521
|
+
data_dir.mkdir()
|
|
522
|
+
console.print("[green]✓[/green] Data directory cleared")
|
|
523
|
+
|
|
524
|
+
# Restart services
|
|
525
|
+
console.print("[cyan]3/3[/cyan] Restarting services...")
|
|
526
|
+
result = subprocess.run(
|
|
527
|
+
["docker-compose", "up", "-d"],
|
|
528
|
+
cwd=zerodb_path,
|
|
529
|
+
capture_output=True,
|
|
530
|
+
text=True
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if result.returncode != 0:
|
|
534
|
+
console.print(f"[red]✗[/red] Failed to restart services")
|
|
535
|
+
console.print(result.stderr)
|
|
536
|
+
sys.exit(1)
|
|
537
|
+
|
|
538
|
+
console.print()
|
|
539
|
+
console.print("[green]✓ Database reset complete[/green]")
|
|
540
|
+
console.print("All data has been deleted and services restarted")
|
|
541
|
+
|
|
542
|
+
except Exception as e:
|
|
543
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
544
|
+
sys.exit(1)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent State CLI Commands
|
|
3
|
+
|
|
4
|
+
Commands for agent state management.
|
|
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="state")
|
|
37
|
+
def state_group():
|
|
38
|
+
"""Agent state management."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@state_group.command(name="get")
|
|
43
|
+
@click.option("--agent-id", required=True, help="Agent ID")
|
|
44
|
+
@click.option("--state-id", help="Specific state ID (optional)")
|
|
45
|
+
def get_state(agent_id: str, state_id: Optional[str]):
|
|
46
|
+
"""Get agent state."""
|
|
47
|
+
try:
|
|
48
|
+
client = get_client()
|
|
49
|
+
result = client.agent_state.get_state(agent_id=agent_id, state_id=state_id)
|
|
50
|
+
|
|
51
|
+
console.print(Panel(
|
|
52
|
+
json.dumps(result, indent=2),
|
|
53
|
+
title=f"Agent State - {agent_id}"
|
|
54
|
+
))
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@state_group.command(name="checkpoint")
|
|
61
|
+
@click.option("--agent-id", required=True, help="Agent ID")
|
|
62
|
+
@click.option("--name", required=True, help="Checkpoint name")
|
|
63
|
+
@click.option("--data", required=True, help="State data (JSON string)")
|
|
64
|
+
@click.option("--description", help="Optional description")
|
|
65
|
+
def create_checkpoint(agent_id: str, name: str, data: str, description: Optional[str]):
|
|
66
|
+
"""Create a state checkpoint."""
|
|
67
|
+
try:
|
|
68
|
+
client = get_client()
|
|
69
|
+
state_data = json.loads(data)
|
|
70
|
+
|
|
71
|
+
result = client.agent_state.create_checkpoint(
|
|
72
|
+
agent_id=agent_id,
|
|
73
|
+
checkpoint_name=name,
|
|
74
|
+
state_data=state_data,
|
|
75
|
+
description=description
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
console.print(Panel(
|
|
79
|
+
f"[green]✓[/green] Checkpoint created: {result.get('checkpoint_id')}",
|
|
80
|
+
title="Success"
|
|
81
|
+
))
|
|
82
|
+
console.print(json.dumps(result, indent=2))
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@state_group.command(name="restore")
|
|
89
|
+
@click.argument("checkpoint_id")
|
|
90
|
+
def restore_checkpoint(checkpoint_id: str):
|
|
91
|
+
"""Restore from a checkpoint."""
|
|
92
|
+
try:
|
|
93
|
+
client = get_client()
|
|
94
|
+
result = client.agent_state.restore_checkpoint(checkpoint_id)
|
|
95
|
+
|
|
96
|
+
console.print(Panel(
|
|
97
|
+
f"[green]✓[/green] State restored from checkpoint",
|
|
98
|
+
title="Success"
|
|
99
|
+
))
|
|
100
|
+
console.print(json.dumps(result, indent=2))
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@state_group.command(name="list")
|
|
107
|
+
@click.option("--agent-id", required=True, help="Agent ID")
|
|
108
|
+
@click.option("--checkpoints", is_flag=True, help="List checkpoints instead of states")
|
|
109
|
+
@click.option("--format", type=click.Choice(["table", "json"]), default="table",
|
|
110
|
+
help="Output format")
|
|
111
|
+
def list_states(agent_id: str, checkpoints: bool, format: str):
|
|
112
|
+
"""List agent states or checkpoints."""
|
|
113
|
+
try:
|
|
114
|
+
client = get_client()
|
|
115
|
+
|
|
116
|
+
if checkpoints:
|
|
117
|
+
result = client.agent_state.list_checkpoints(agent_id=agent_id)
|
|
118
|
+
items = result.get("checkpoints", [])
|
|
119
|
+
title = "Checkpoints"
|
|
120
|
+
else:
|
|
121
|
+
result = client.agent_state.list_states(agent_id=agent_id)
|
|
122
|
+
items = result.get("states", [])
|
|
123
|
+
title = "States"
|
|
124
|
+
|
|
125
|
+
if format == "json":
|
|
126
|
+
click.echo(json.dumps(items, indent=2))
|
|
127
|
+
else:
|
|
128
|
+
table = Table(title=title)
|
|
129
|
+
table.add_column("ID", style="cyan")
|
|
130
|
+
table.add_column("Name", style="bold")
|
|
131
|
+
table.add_column("Created", style="dim")
|
|
132
|
+
|
|
133
|
+
for item in items:
|
|
134
|
+
table.add_row(
|
|
135
|
+
item.get("id", "")[:12],
|
|
136
|
+
item.get("name", item.get("checkpoint_name", "")),
|
|
137
|
+
item.get("created_at", "")
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
console.print(table)
|
|
141
|
+
console.print(f"\nTotal: {len(items)} {title.lower()}")
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
click.echo(f"Error: {str(e)}", err=True)
|