refactor: flatten CLI directory structure - remove 'box in a box'
BEFORE: /opt/aitbc/cli/ ├── aitbc_cli/ # Python package (box in a box) │ ├── commands/ │ ├── main.py │ └── ... ├── setup.py AFTER: /opt/aitbc/cli/ # Flat structure ├── commands/ # Direct access ├── main.py # Direct access ├── auth/ ├── config/ ├── core/ ├── models/ ├── utils/ ├── plugins.py └── setup.py CHANGES MADE: - Moved all files from aitbc_cli/ to cli/ root - Fixed all relative imports (from . to absolute imports) - Updated setup.py entry point: aitbc_cli.main → main - Added CLI directory to Python path in entry script - Simplified deployment.py to remove dependency on deleted core.deployment - Fixed import paths in all command files - Recreated virtual environment with new structure BENEFITS: - Eliminated 'box in a box' nesting - Simpler directory structure - Direct access to all modules - Cleaner imports - Easier maintenance and development - CLI works with both 'python main.py' and 'aitbc' commands
This commit is contained in:
603
cli/commands/openclaw.py
Executable file
603
cli/commands/openclaw.py
Executable file
@@ -0,0 +1,603 @@
|
||||
"""OpenClaw integration commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def openclaw():
|
||||
"""OpenClaw integration with edge computing deployment"""
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def deploy():
|
||||
"""Agent deployment operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(deploy)
|
||||
|
||||
|
||||
@deploy.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--region", required=True, help="Deployment region")
|
||||
@click.option("--instances", default=1, help="Number of instances to deploy")
|
||||
@click.option("--instance-type", default="standard", help="Instance type")
|
||||
@click.option("--edge-locations", help="Comma-separated edge locations")
|
||||
@click.option("--auto-scale", is_flag=True, help="Enable auto-scaling")
|
||||
@click.pass_context
|
||||
def deploy_agent(ctx, agent_id: str, region: str, instances: int, instance_type: str,
|
||||
edge_locations: Optional[str], auto_scale: bool):
|
||||
"""Deploy agent to OpenClaw network"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
deployment_data = {
|
||||
"agent_id": agent_id,
|
||||
"region": region,
|
||||
"instances": instances,
|
||||
"instance_type": instance_type,
|
||||
"auto_scale": auto_scale
|
||||
}
|
||||
|
||||
if edge_locations:
|
||||
deployment_data["edge_locations"] = [loc.strip() for loc in edge_locations.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/deploy",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=deployment_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
deployment = response.json()
|
||||
success(f"Agent deployment started: {deployment['id']}")
|
||||
output(deployment, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--instances", required=True, type=int, help="New number of instances")
|
||||
@click.option("--auto-scale", is_flag=True, help="Enable auto-scaling")
|
||||
@click.option("--min-instances", default=1, help="Minimum instances for auto-scaling")
|
||||
@click.option("--max-instances", default=10, help="Maximum instances for auto-scaling")
|
||||
@click.pass_context
|
||||
def scale(ctx, deployment_id: str, instances: int, auto_scale: bool, min_instances: int, max_instances: int):
|
||||
"""Scale agent deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
scale_data = {
|
||||
"instances": instances,
|
||||
"auto_scale": auto_scale,
|
||||
"min_instances": min_instances,
|
||||
"max_instances": max_instances
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/deployments/{deployment_id}/scale",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=scale_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment scaled successfully")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to scale deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@deploy.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--objective", default="cost",
|
||||
type=click.Choice(["cost", "performance", "latency", "efficiency"]),
|
||||
help="Optimization objective")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, objective: str):
|
||||
"""Optimize agent deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {"objective": objective}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def monitor():
|
||||
"""OpenClaw monitoring operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(monitor)
|
||||
|
||||
|
||||
@monitor.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--metrics", default="latency,cost", help="Comma-separated metrics to monitor")
|
||||
@click.option("--real-time", is_flag=True, help="Show real-time metrics")
|
||||
@click.option("--interval", default=10, help="Update interval for real-time monitoring")
|
||||
@click.pass_context
|
||||
def monitor_metrics(ctx, deployment_id: str, metrics: str, real_time: bool, interval: int):
|
||||
"""Monitor OpenClaw agent performance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"metrics": [m.strip() for m in metrics.split(',')]}
|
||||
|
||||
def get_metrics():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/deployments/{deployment_id}/metrics",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get metrics: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if real_time:
|
||||
click.echo(f"Monitoring deployment {deployment_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
metrics_data = get_metrics()
|
||||
if metrics_data:
|
||||
click.clear()
|
||||
click.echo(f"Deployment ID: {deployment_id}")
|
||||
click.echo(f"Status: {metrics_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Instances: {metrics_data.get('instances', 'N/A')}")
|
||||
|
||||
metrics_list = metrics_data.get('metrics', {})
|
||||
for metric in [m.strip() for m in metrics.split(',')]:
|
||||
if metric in metrics_list:
|
||||
value = metrics_list[metric]
|
||||
click.echo(f"{metric.title()}: {value}")
|
||||
|
||||
if metrics_data.get('status') in ['terminated', 'failed']:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
else:
|
||||
metrics_data = get_metrics()
|
||||
if metrics_data:
|
||||
output(metrics_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@monitor.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def status(ctx, deployment_id: str):
|
||||
"""Get deployment status"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/deployments/{deployment_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status_data = response.json()
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get deployment status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def edge():
|
||||
"""Edge computing operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(edge)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--locations", required=True, help="Comma-separated edge locations")
|
||||
@click.option("--strategy", default="latency",
|
||||
type=click.Choice(["latency", "cost", "availability", "hybrid"]),
|
||||
help="Edge deployment strategy")
|
||||
@click.option("--replicas", default=1, help="Number of replicas per location")
|
||||
@click.pass_context
|
||||
def deploy(ctx, agent_id: str, locations: str, strategy: str, replicas: int):
|
||||
"""Deploy agent to edge locations"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
edge_data = {
|
||||
"agent_id": agent_id,
|
||||
"locations": [loc.strip() for loc in locations.split(',')],
|
||||
"strategy": strategy,
|
||||
"replicas": replicas
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/edge/deploy",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=edge_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
deployment = response.json()
|
||||
success(f"Edge deployment started: {deployment['id']}")
|
||||
output(deployment, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start edge deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.option("--location", help="Filter by location")
|
||||
@click.pass_context
|
||||
def resources(ctx, location: Optional[str]):
|
||||
"""Manage edge resources"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if location:
|
||||
params["location"] = location
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/edge/resources",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
resources = response.json()
|
||||
output(resources, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get edge resources: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--latency-target", type=int, help="Target latency in milliseconds")
|
||||
@click.option("--cost-budget", type=float, help="Cost budget")
|
||||
@click.option("--availability", type=float, help="Target availability (0.0-1.0)")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, latency_target: Optional[int],
|
||||
cost_budget: Optional[float], availability: Optional[float]):
|
||||
"""Optimize edge deployment performance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {}
|
||||
if latency_target:
|
||||
optimization_data["latency_target_ms"] = latency_target
|
||||
if cost_budget:
|
||||
optimization_data["cost_budget"] = cost_budget
|
||||
if availability:
|
||||
optimization_data["availability_target"] = availability
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/edge/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Edge optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize edge deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--standards", help="Comma-separated compliance standards")
|
||||
@click.pass_context
|
||||
def compliance(ctx, deployment_id: str, standards: Optional[str]):
|
||||
"""Check edge security compliance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if standards:
|
||||
params["standards"] = [s.strip() for s in standards.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/edge/deployments/{deployment_id}/compliance",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
compliance_data = response.json()
|
||||
output(compliance_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to check compliance: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def routing():
|
||||
"""Agent skill routing and job offloading"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(routing)
|
||||
|
||||
|
||||
@routing.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--algorithm", default="load-balanced",
|
||||
type=click.Choice(["load-balanced", "skill-based", "cost-based", "latency-based"]),
|
||||
help="Routing algorithm")
|
||||
@click.option("--weights", help="Comma-separated weights for routing factors")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, algorithm: str, weights: Optional[str]):
|
||||
"""Optimize agent skill routing"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
routing_data = {"algorithm": algorithm}
|
||||
if weights:
|
||||
routing_data["weights"] = [w.strip() for w in weights.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/routing/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=routing_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Routing optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize routing: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@routing.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def status(ctx, deployment_id: str):
|
||||
"""Get routing status and statistics"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/routing/deployments/{deployment_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status_data = response.json()
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get routing status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def ecosystem():
|
||||
"""OpenClaw ecosystem development"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(ecosystem)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.option("--name", required=True, help="Solution name")
|
||||
@click.option("--type", required=True,
|
||||
type=click.Choice(["agent", "workflow", "integration", "tool"]),
|
||||
help="Solution type")
|
||||
@click.option("--description", default="", help="Solution description")
|
||||
@click.option("--package", type=click.File('rb'), help="Solution package file")
|
||||
@click.pass_context
|
||||
def create(ctx, name: str, type: str, description: str, package):
|
||||
"""Create OpenClaw ecosystem solution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
solution_data = {
|
||||
"name": name,
|
||||
"type": type,
|
||||
"description": description
|
||||
}
|
||||
|
||||
files = {}
|
||||
if package:
|
||||
files["package"] = package.read()
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/ecosystem/solutions",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=solution_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
solution = response.json()
|
||||
success(f"OpenClaw solution created: {solution['id']}")
|
||||
output(solution, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create solution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.option("--type", help="Filter by solution type")
|
||||
@click.option("--category", help="Filter by category")
|
||||
@click.option("--limit", default=20, help="Number of solutions to list")
|
||||
@click.pass_context
|
||||
def list(ctx, type: Optional[str], category: Optional[str], limit: int):
|
||||
"""List OpenClaw ecosystem solutions"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"limit": limit}
|
||||
if type:
|
||||
params["type"] = type
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/openclaw/ecosystem/solutions",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
solutions = response.json()
|
||||
output(solutions, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to list solutions: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.argument("solution_id")
|
||||
@click.pass_context
|
||||
def install(ctx, solution_id: str):
|
||||
"""Install OpenClaw ecosystem solution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/openclaw/ecosystem/solutions/{solution_id}/install",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Solution installed successfully")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to install solution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@openclaw.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def terminate(ctx, deployment_id: str):
|
||||
"""Terminate OpenClaw deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
if not click.confirm(f"Terminate deployment {deployment_id}? This action cannot be undone."):
|
||||
click.echo("Operation cancelled")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.delete(
|
||||
f"{config.coordinator_url}/openclaw/deployments/{deployment_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment {deployment_id} terminated")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to terminate deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
Reference in New Issue
Block a user