Implement 36 missing CLI subcommands for scenarios 04-47
Phase 1: Simple GET Commands (system, resource, edge, config) - system: restart, status, config - resource: status, deallocate - edge: status, balance, transfer - config: import (already existed) Phase 2: Exchange & Market Commands - exchange: order, orders, book, history - marketplace: bid, bids, ask, asks Phase 3: AI & GPU Commands - agent (AI): job, jobs, submit, cancel - gpu: register, update Phase 4: Wallet, Hermes, Operations - wallet: export, import - hermes: send, receive, peers - operations: vote, proposal, delegate Phase 5: Simulate Commands - simulate: run, status, result All commands follow common pattern using AITBCHTTPClient for backend API interaction.
This commit is contained in:
@@ -19,6 +19,12 @@ except ImportError:
|
||||
ComputeConsumer = None
|
||||
AITBCAgent = None
|
||||
|
||||
from ..utils import output, error, success
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_agent_config_dir() -> Path:
|
||||
"""Get the agent configuration directory"""
|
||||
@@ -602,6 +608,88 @@ try:
|
||||
error(f"Error exporting configuration: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@agent.command()
|
||||
@click.argument('job_id')
|
||||
@click.pass_context
|
||||
def job(ctx, job_id: str):
|
||||
"""Get specific AI job details from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
job_data = http_client.get(f"/api/v1/jobs/{job_id}")
|
||||
success(f"Job {job_id}:")
|
||||
output(job_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching job: {e}")
|
||||
|
||||
@agent.command()
|
||||
@click.option('--status', help='Filter by job status')
|
||||
@click.option('--limit', type=int, default=20, help='Number of jobs to return')
|
||||
@click.pass_context
|
||||
def jobs(ctx, status: Optional[str], limit: int):
|
||||
"""List AI jobs from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
params = {"limit": limit}
|
||||
if status:
|
||||
params["status"] = status
|
||||
|
||||
jobs_data = http_client.get("/api/v1/jobs", params=params)
|
||||
success("Jobs:")
|
||||
output(jobs_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching jobs: {e}")
|
||||
|
||||
@agent.command()
|
||||
@click.argument('task')
|
||||
@click.option('--model', help='AI model to use')
|
||||
@click.option('--priority', default='normal', help='Job priority')
|
||||
@click.pass_context
|
||||
def submit(ctx, task: str, model: Optional[str], priority: str):
|
||||
"""Submit an AI job to coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
job_data = {
|
||||
"task": task,
|
||||
"priority": priority
|
||||
}
|
||||
if model:
|
||||
job_data["model"] = model
|
||||
|
||||
result = http_client.post("/api/v1/jobs", json=job_data)
|
||||
success(f"Job submitted: {result.get('job_id')}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error submitting job: {e}")
|
||||
|
||||
@agent.command()
|
||||
@click.argument('job_id')
|
||||
@click.pass_context
|
||||
def cancel(ctx, job_id: str):
|
||||
"""Cancel an AI job via coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
result = http_client.delete(f"/api/v1/jobs/{job_id}")
|
||||
success(f"Job {job_id} cancelled")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error cancelling job: {e}")
|
||||
|
||||
except ImportError:
|
||||
# Click not available, commands will be added programmatically
|
||||
pass
|
||||
|
||||
@@ -8,9 +8,10 @@ import httpx
|
||||
from typing import Optional
|
||||
from ..utils import output, error, success, info, warning
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
# Initialize logger
|
||||
logger = None
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -19,6 +20,67 @@ def edge():
|
||||
pass
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.pass_context
|
||||
def status(ctx):
|
||||
"""Get edge status from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
status_data = http_client.get("/edge-gpu/metrics")
|
||||
success("Edge Status:")
|
||||
output(status_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching edge status: {e}")
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.pass_context
|
||||
def balance(ctx):
|
||||
"""Get edge wallet balance from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
balance_data = http_client.get("/edge-gpu/balance")
|
||||
success("Edge Wallet Balance:")
|
||||
output(balance_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching edge balance: {e}")
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument('to_address')
|
||||
@click.argument('amount', type=float)
|
||||
@click.option('--note', help='Transfer note')
|
||||
@click.pass_context
|
||||
def transfer(ctx, to_address: str, amount: float, note: Optional[str]):
|
||||
"""Transfer edge tokens to another address"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
transfer_data = {
|
||||
"to_address": to_address,
|
||||
"amount": amount
|
||||
}
|
||||
if note:
|
||||
transfer_data["note"] = note
|
||||
|
||||
result = http_client.post("/edge-gpu/transfer", json=transfer_data)
|
||||
success(f"Transfer of {amount} to {to_address} submitted")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error executing transfer: {e}")
|
||||
|
||||
|
||||
def get_edge_client():
|
||||
"""Get Edge API HTTP client"""
|
||||
config = get_config()
|
||||
|
||||
@@ -877,11 +877,92 @@ def pairs(ctx, exchange: str):
|
||||
|
||||
if len(base_currencies[base]) > 10:
|
||||
success(f" ... and {len(base_currencies[base]) - 10} more")
|
||||
|
||||
except ImportError:
|
||||
error("❌ Real exchange integration not available. Install ccxt library.")
|
||||
|
||||
|
||||
@exchange.command()
|
||||
@click.argument('order_id')
|
||||
@click.pass_context
|
||||
def order(ctx, order_id: str):
|
||||
"""Get specific order details from exchange-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.exchange_service_url, timeout=10)
|
||||
order_data = http_client.get(f"/exchange/order/{order_id}")
|
||||
success(f"Order {order_id}:")
|
||||
output(order_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"❌ Pairs error: {e}")
|
||||
error(f"Error fetching order: {e}")
|
||||
|
||||
|
||||
@exchange.command()
|
||||
@click.option('--pair', help='Filter by trading pair')
|
||||
@click.option('--status', help='Filter by status')
|
||||
@click.option('--limit', type=int, default=20, help='Number of orders to return')
|
||||
@click.pass_context
|
||||
def orders(ctx, pair: Optional[str], status: Optional[str], limit: int):
|
||||
"""List orders from exchange-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.exchange_service_url, timeout=10)
|
||||
params = {"limit": limit}
|
||||
if pair:
|
||||
params["pair"] = pair
|
||||
if status:
|
||||
params["status"] = status
|
||||
|
||||
orders_data = http_client.get("/exchange/orders", params=params)
|
||||
success("Orders:")
|
||||
output(orders_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching orders: {e}")
|
||||
|
||||
|
||||
@exchange.command()
|
||||
@click.option('--pair', required=True, help='Trading pair (e.g., AITBC/BTC)')
|
||||
@click.option('--limit', type=int, default=20, help='Order book depth')
|
||||
@click.pass_context
|
||||
def book(ctx, pair: str, limit: int):
|
||||
"""Get order book from exchange-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.exchange_service_url, timeout=10)
|
||||
book_data = http_client.get("/exchange/orderbook", params={"pair": pair, "limit": limit})
|
||||
success(f"Order Book for {pair}:")
|
||||
output(book_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching order book: {e}")
|
||||
|
||||
|
||||
@exchange.command()
|
||||
@click.option('--pair', help='Filter by trading pair')
|
||||
@click.option('--limit', type=int, default=50, help='Number of history entries')
|
||||
@click.pass_context
|
||||
def history(ctx, pair: Optional[str], limit: int):
|
||||
"""Get trade history from exchange-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.exchange_service_url, timeout=10)
|
||||
params = {"limit": limit}
|
||||
if pair:
|
||||
params["pair"] = pair
|
||||
|
||||
history_data = http_client.get("/exchange/history", params=params)
|
||||
success("Trade History:")
|
||||
output(history_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching trade history: {e}")
|
||||
|
||||
|
||||
@exchange.command()
|
||||
|
||||
@@ -670,3 +670,86 @@ def providers(ctx):
|
||||
except Exception as e:
|
||||
error(f"Error querying GPU providers: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@gpu.command()
|
||||
@click.argument('gpu_id')
|
||||
@click.option('--specs', help='GPU specifications (JSON string)')
|
||||
@click.option('--pricing', help='Pricing model (JSON string)')
|
||||
@click.pass_context
|
||||
def register(ctx, gpu_id: str, specs: Optional[str], pricing: Optional[str]):
|
||||
"""Register a GPU with the gpu-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.gpu_service_url, timeout=10)
|
||||
|
||||
gpu_data = {"gpu_id": gpu_id}
|
||||
|
||||
if specs:
|
||||
try:
|
||||
gpu_data["specs"] = json.loads(specs)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON specifications")
|
||||
raise click.Abort()
|
||||
|
||||
if pricing:
|
||||
try:
|
||||
gpu_data["pricing"] = json.loads(pricing)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON pricing")
|
||||
raise click.Abort()
|
||||
|
||||
result = http_client.post("/gpu/register", json=gpu_data)
|
||||
success(f"GPU {gpu_id} registered successfully")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error registering GPU: {e}")
|
||||
|
||||
|
||||
@gpu.command()
|
||||
@click.argument('gpu_id')
|
||||
@click.option('--specs', help='Updated GPU specifications (JSON string)')
|
||||
@click.option('--pricing', help='Updated pricing model (JSON string)')
|
||||
@click.option('--status', help='Update GPU status')
|
||||
@click.pass_context
|
||||
def update(ctx, gpu_id: str, specs: Optional[str], pricing: Optional[str], status: Optional[str]):
|
||||
"""Update GPU registration with the gpu-service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.gpu_service_url, timeout=10)
|
||||
|
||||
update_data = {}
|
||||
|
||||
if specs:
|
||||
try:
|
||||
update_data["specs"] = json.loads(specs)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON specifications")
|
||||
raise click.Abort()
|
||||
|
||||
if pricing:
|
||||
try:
|
||||
update_data["pricing"] = json.loads(pricing)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON pricing")
|
||||
raise click.Abort()
|
||||
|
||||
if status:
|
||||
update_data["status"] = status
|
||||
|
||||
if not update_data:
|
||||
error("No updates provided. Specify --specs, --pricing, or --status")
|
||||
raise click.Abort()
|
||||
|
||||
result = http_client.put(f"/gpu/{gpu_id}", json=update_data)
|
||||
success(f"GPU {gpu_id} updated successfully")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error updating GPU: {e}")
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from ..utils import error, success, output
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -156,3 +160,66 @@ def stop(agent_id: Optional[str]):
|
||||
"""Stop Hermes training"""
|
||||
success(f"Stop Hermes training for agent {agent_id}")
|
||||
# TODO: Implement actual stop command via coordinator API
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.argument('message')
|
||||
@click.option('--to-agent', help='Target agent ID')
|
||||
@click.option('--priority', default='normal', help='Message priority')
|
||||
@click.pass_context
|
||||
def send(ctx, message: str, to_agent: Optional[str], priority: str):
|
||||
"""Send a message via hermes service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.hermes_service_url, timeout=10)
|
||||
message_data = {
|
||||
"message": message,
|
||||
"priority": priority
|
||||
}
|
||||
if to_agent:
|
||||
message_data["to_agent"] = to_agent
|
||||
|
||||
result = http_client.post("/hermes/send", json=message_data)
|
||||
success("Message sent via hermes")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error sending message: {e}")
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.option('--limit', type=int, default=20, help='Number of messages to return')
|
||||
@click.pass_context
|
||||
def receive(ctx, limit: int):
|
||||
"""Receive messages from hermes service"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.hermes_service_url, timeout=10)
|
||||
messages_data = http_client.get("/hermes/messages", params={"limit": limit})
|
||||
success("Messages:")
|
||||
output(messages_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error receiving messages: {e}")
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.pass_context
|
||||
def peers(ctx):
|
||||
"""List hermes service peers"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.hermes_service_url, timeout=10)
|
||||
peers_data = http_client.get("/hermes/peers")
|
||||
success("Hermes Peers:")
|
||||
output(peers_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching peers: {e}")
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ from ..core.marketplace import (
|
||||
)
|
||||
from ..utils import output, error, success
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@click.group()
|
||||
@click.option("--chain-id", help="Chain ID for multichain operations (e.g., ait-mainnet, ait-devnet)")
|
||||
@@ -517,3 +520,100 @@ def monitor(ctx, realtime, interval):
|
||||
except Exception as e:
|
||||
error(f"Error during monitoring: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('price', type=float)
|
||||
@click.argument('quantity', type=float)
|
||||
@click.option('--market', help='Market identifier')
|
||||
@click.pass_context
|
||||
def bid(ctx, price: float, quantity: float, market: Optional[str]):
|
||||
"""Place a bid in the marketplace"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.marketplace_service_url, timeout=10)
|
||||
bid_data = {
|
||||
"price": price,
|
||||
"quantity": quantity,
|
||||
"market": market or "default"
|
||||
}
|
||||
result = http_client.post("/marketplace/bid", json=bid_data)
|
||||
success(f"Bid placed: {quantity} @ {price}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error placing bid: {e}")
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--market', help='Filter by market')
|
||||
@click.option('--limit', type=int, default=20, help='Number of bids to return')
|
||||
@click.pass_context
|
||||
def bids(ctx, market: Optional[str], limit: int):
|
||||
"""List bids from the marketplace"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.marketplace_service_url, timeout=10)
|
||||
params = {"limit": limit}
|
||||
if market:
|
||||
params["market"] = market
|
||||
|
||||
bids_data = http_client.get("/marketplace/bids", params=params)
|
||||
success("Bids:")
|
||||
output(bids_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching bids: {e}")
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('price', type=float)
|
||||
@click.argument('quantity', type=float)
|
||||
@click.option('--market', help='Market identifier')
|
||||
@click.pass_context
|
||||
def ask(ctx, price: float, quantity: float, market: Optional[str]):
|
||||
"""Place an ask in the marketplace"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.marketplace_service_url, timeout=10)
|
||||
ask_data = {
|
||||
"price": price,
|
||||
"quantity": quantity,
|
||||
"market": market or "default"
|
||||
}
|
||||
result = http_client.post("/marketplace/ask", json=ask_data)
|
||||
success(f"Ask placed: {quantity} @ {price}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error placing ask: {e}")
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--market', help='Filter by market')
|
||||
@click.option('--limit', type=int, default=20, help='Number of asks to return')
|
||||
@click.pass_context
|
||||
def asks(ctx, market: Optional[str], limit: int):
|
||||
"""List asks from the marketplace"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.marketplace_service_url, timeout=10)
|
||||
params = {"limit": limit}
|
||||
if market:
|
||||
params["market"] = market
|
||||
|
||||
asks_data = http_client.get("/marketplace/asks", params=params)
|
||||
success("Asks:")
|
||||
output(asks_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching asks: {e}")
|
||||
|
||||
|
||||
@@ -10,12 +10,15 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from ..utils import error, success, output
|
||||
from ..utils.wallet import decrypt_private_key
|
||||
from aitbc import AITBCHTTPClient, NetworkError, KEYSTORE_DIR
|
||||
from ..config import get_config
|
||||
from aitbc import AITBCHTTPClient, NetworkError, KEYSTORE_DIR, get_logger
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
DEFAULT_RPC_URL = "http://localhost:8006"
|
||||
DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR
|
||||
|
||||
@@ -357,3 +360,88 @@ def message(agent: str, message: str, wallet: str, password: Optional[str], pass
|
||||
except Exception as e:
|
||||
error(f"Error sending message: {e}")
|
||||
|
||||
|
||||
# Governance operations
|
||||
@operations.group()
|
||||
def governance():
|
||||
"""Governance operations"""
|
||||
pass
|
||||
|
||||
|
||||
@governance.command()
|
||||
@click.argument('proposal_id')
|
||||
@click.option('--vote', type=click.Choice(['yes', 'no', 'abstain']), required=True, help='Vote option')
|
||||
@click.option('--wallet', required=True, help='Wallet name for signing')
|
||||
@click.pass_context
|
||||
def vote(ctx, proposal_id: str, vote: str, wallet: str):
|
||||
"""Vote on a governance proposal"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.governance_service_url, timeout=10)
|
||||
vote_data = {
|
||||
"proposal_id": proposal_id,
|
||||
"vote": vote,
|
||||
"wallet": wallet
|
||||
}
|
||||
result = http_client.post("/governance/vote", json=vote_data)
|
||||
success(f"Vote '{vote}' cast for proposal {proposal_id}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error casting vote: {e}")
|
||||
|
||||
|
||||
@governance.command()
|
||||
@click.option('--title', required=True, help='Proposal title')
|
||||
@click.option('--description', required=True, help='Proposal description')
|
||||
@click.option('--type', default='parameter', help='Proposal type')
|
||||
@click.option('--wallet', required=True, help='Wallet name for signing')
|
||||
@click.pass_context
|
||||
def proposal(ctx, title: str, description: str, type: str, wallet: str):
|
||||
"""Create a governance proposal"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.governance_service_url, timeout=10)
|
||||
proposal_data = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"type": type,
|
||||
"wallet": wallet
|
||||
}
|
||||
result = http_client.post("/governance/proposals", json=proposal_data)
|
||||
success(f"Proposal created: {result.get('proposal_id')}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error creating proposal: {e}")
|
||||
|
||||
|
||||
@governance.command()
|
||||
@click.argument('to_address')
|
||||
@click.option('--amount', type=float, required=True, help='Amount to delegate')
|
||||
@click.option('--wallet', required=True, help='Wallet name for signing')
|
||||
@click.pass_context
|
||||
def delegate(ctx, to_address: str, amount: float, wallet: str):
|
||||
"""Delegate voting power to another address"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.governance_service_url, timeout=10)
|
||||
delegate_data = {
|
||||
"to_address": to_address,
|
||||
"amount": amount,
|
||||
"wallet": wallet
|
||||
}
|
||||
result = http_client.post("/governance/delegate", json=delegate_data)
|
||||
success(f"Delegated {amount} to {to_address}")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error delegating: {e}")
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from ..utils import error, success, output
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -126,3 +130,50 @@ def optimize(target: str, agent_id: Optional[str], mock: bool):
|
||||
click.echo("Improvement: 12.5%")
|
||||
click.echo("Status: Optimized")
|
||||
return 0
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.option('--resource-id', help='Specific resource ID to check')
|
||||
@click.pass_context
|
||||
def status(ctx, resource_id: Optional[str]):
|
||||
"""Get resource allocation status from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
|
||||
if resource_id:
|
||||
status_data = http_client.get(f"/api/v1/resources/{resource_id}/status")
|
||||
else:
|
||||
status_data = http_client.get("/api/v1/resources/status")
|
||||
|
||||
success("Resource Status:")
|
||||
output(status_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching resource status: {e}")
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource_id')
|
||||
@click.option('--force', is_flag=True, help='Force deallocation without confirmation')
|
||||
@click.pass_context
|
||||
def deallocate(ctx, resource_id: str, force: bool):
|
||||
"""Deallocate resources via coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
if not force:
|
||||
if not click.confirm(f"Are you sure you want to deallocate resource {resource_id}?"):
|
||||
return
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
result = http_client.post(f"/api/v1/resources/{resource_id}/deallocate")
|
||||
success(f"Resource {resource_id} deallocated successfully")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error deallocating resource: {e}")
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import click
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Any, List
|
||||
from typing import Dict, Any, List, Optional
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -26,6 +26,10 @@ except ImportError:
|
||||
def get_config(config_file=None, role=None):
|
||||
return {}
|
||||
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@click.group()
|
||||
def simulate():
|
||||
@@ -338,5 +342,73 @@ def ai_jobs(jobs, models, duration_range):
|
||||
click.echo(f" {model}: {count} jobs")
|
||||
|
||||
|
||||
@simulate.command()
|
||||
@click.argument('scenario')
|
||||
@click.option('--params', help='Simulation parameters (JSON string)')
|
||||
@click.option('--async-run', is_flag=True, help='Run simulation asynchronously')
|
||||
@click.pass_context
|
||||
def run(ctx, scenario: str, params: Optional[str], async_run: bool):
|
||||
"""Run a simulation scenario via coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
sim_data = {
|
||||
"scenario": scenario,
|
||||
"async": async_run
|
||||
}
|
||||
|
||||
if params:
|
||||
try:
|
||||
sim_data["params"] = json.loads(params)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON parameters")
|
||||
raise click.Abort()
|
||||
|
||||
result = http_client.post("/simulate/run", json=sim_data)
|
||||
success(f"Simulation '{scenario}' started")
|
||||
output(result, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error running simulation: {e}")
|
||||
|
||||
|
||||
@simulate.command()
|
||||
@click.argument('simulation_id')
|
||||
@click.pass_context
|
||||
def status(ctx, simulation_id: str):
|
||||
"""Get simulation status from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
status_data = http_client.get(f"/simulate/{simulation_id}/status")
|
||||
success(f"Simulation {simulation_id} Status:")
|
||||
output(status_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching simulation status: {e}")
|
||||
|
||||
|
||||
@simulate.command()
|
||||
@click.argument('simulation_id')
|
||||
@click.pass_context
|
||||
def result(ctx, simulation_id: str):
|
||||
"""Get simulation results from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
result_data = http_client.get(f"/simulate/{simulation_id}/result")
|
||||
success(f"Simulation {simulation_id} Results:")
|
||||
output(result_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching simulation results: {e}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
simulate()
|
||||
|
||||
@@ -5,6 +5,13 @@ System commands for AITBC CLI
|
||||
|
||||
import click
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from ..utils import output, error, success
|
||||
from ..config import get_config
|
||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@click.group()
|
||||
def system():
|
||||
@@ -50,5 +57,80 @@ def check(service):
|
||||
else:
|
||||
click.echo(f"❌ {svc}: {service_file}")
|
||||
|
||||
|
||||
@system.command()
|
||||
@click.option('--service', required=True, help='Service to restart (e.g., blockchain-node, wallet)')
|
||||
@click.pass_context
|
||||
def restart(ctx, service: str):
|
||||
"""Restart a systemd service"""
|
||||
service_name = f"aitbc-{service}" if not service.startswith("aitbc-") else service
|
||||
|
||||
try:
|
||||
success(f"Restarting service: {service_name}")
|
||||
result = subprocess.run(
|
||||
["sudo", "systemctl", "restart", service_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
success(f"Service {service_name} restarted successfully")
|
||||
output({"service": service_name, "status": "restarted"}, ctx.obj.get("output_format", "table"))
|
||||
else:
|
||||
error(f"Failed to restart service: {result.stderr}")
|
||||
except subprocess.TimeoutExpired:
|
||||
error(f"Timeout restarting service {service_name}")
|
||||
except Exception as e:
|
||||
error(f"Error restarting service: {e}")
|
||||
|
||||
|
||||
@system.command()
|
||||
@click.pass_context
|
||||
def status(ctx):
|
||||
"""Get system status from coordinator-api"""
|
||||
config = get_config()
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=config.coordinator_url, timeout=10)
|
||||
status_data = http_client.get("/api/v1/status")
|
||||
success("System Status:")
|
||||
output(status_data, ctx.obj.get("output_format", "table"))
|
||||
except NetworkError as e:
|
||||
error(f"Network error: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error fetching status: {e}")
|
||||
|
||||
|
||||
@system.command()
|
||||
@click.option('--show-secrets', is_flag=True, help='Show sensitive values like API keys')
|
||||
@click.pass_context
|
||||
def config(ctx, show_secrets: bool):
|
||||
"""Display system configuration from /etc/aitbc/blockchain.env"""
|
||||
config_path = Path("/etc/aitbc/blockchain.env")
|
||||
|
||||
if not config_path.exists():
|
||||
error(f"Configuration file not found: {config_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config_lines = f.readlines()
|
||||
|
||||
config_data = {}
|
||||
for line in config_lines:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
# Hide secrets unless explicitly requested
|
||||
if not show_secrets and any(secret in key.lower() for secret in ['key', 'secret', 'password', 'token']):
|
||||
value = '***HIDDEN***'
|
||||
config_data[key.strip()] = value.strip()
|
||||
|
||||
success("System Configuration:")
|
||||
output(config_data, ctx.obj.get("output_format", "table"))
|
||||
except Exception as e:
|
||||
error(f"Error reading configuration: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
system()
|
||||
|
||||
@@ -1561,3 +1561,84 @@ def fund(ctx, address: str, amount: int, chain_id: str):
|
||||
error(f"HTTP error calling faucet: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error funding wallet: {e}")
|
||||
|
||||
|
||||
@wallet.command()
|
||||
@click.option('--destination', help='Destination file path (default: wallet_name_export.json)')
|
||||
@click.pass_context
|
||||
def export(ctx, destination: Optional[str]):
|
||||
"""Export wallet to JSON file"""
|
||||
wallet_name = ctx.obj["wallet_name"]
|
||||
wallet_path = ctx.obj["wallet_path"]
|
||||
|
||||
if not wallet_path.exists():
|
||||
error(f"Wallet '{wallet_name}' not found")
|
||||
return
|
||||
|
||||
try:
|
||||
wallet_data = _load_wallet(wallet_path, wallet_name)
|
||||
|
||||
# Generate export filename if not provided
|
||||
if not destination:
|
||||
destination = f"{wallet_name}_export.json"
|
||||
|
||||
export_path = Path(destination)
|
||||
|
||||
# Write export file
|
||||
with open(export_path, 'w') as f:
|
||||
json.dump(wallet_data, f, indent=2)
|
||||
|
||||
success(f"Wallet exported to {export_path}")
|
||||
output({
|
||||
"wallet": wallet_name,
|
||||
"exported_to": str(export_path),
|
||||
"address": wallet_data.get("address"),
|
||||
"balance": wallet_data.get("balance", 0)
|
||||
}, ctx.obj.get("output_format", "table"))
|
||||
except Exception as e:
|
||||
error(f"Error exporting wallet: {e}")
|
||||
|
||||
|
||||
@wallet.command()
|
||||
@click.argument('file_path')
|
||||
@click.option('--name', help='New wallet name (default: from file)')
|
||||
@click.pass_context
|
||||
def import_wallet(ctx, file_path: str, name: Optional[str]):
|
||||
"""Import wallet from JSON file"""
|
||||
wallet_dir = ctx.obj.get("wallet_dir", Path.home() / ".aitbc" / "wallets")
|
||||
wallet_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
import_path = Path(file_path)
|
||||
|
||||
if not import_path.exists():
|
||||
error(f"Import file not found: {file_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(import_path, 'r') as f:
|
||||
wallet_data = json.load(f)
|
||||
|
||||
# Determine wallet name
|
||||
wallet_name = name or wallet_data.get("name", import_path.stem)
|
||||
wallet_path = wallet_dir / f"{wallet_name}.json"
|
||||
|
||||
if wallet_path.exists():
|
||||
if not click.confirm(f"Wallet '{wallet_name}' already exists. Overwrite?"):
|
||||
return
|
||||
|
||||
# Save imported wallet
|
||||
with open(wallet_path, 'w') as f:
|
||||
json.dump(wallet_data, f, indent=2)
|
||||
|
||||
success(f"Wallet imported as '{wallet_name}'")
|
||||
output({
|
||||
"wallet": wallet_name,
|
||||
"imported_from": str(import_path),
|
||||
"address": wallet_data.get("address"),
|
||||
"balance": wallet_data.get("balance", 0)
|
||||
}, ctx.obj.get("output_format", "table"))
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON file")
|
||||
except Exception as e:
|
||||
error(f"Error importing wallet: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user