refactor: move version to separate module and improve logging
Some checks failed
CLI Tests / test-cli (push) Failing after 4s
Deploy to Testnet / deploy-testnet (push) Successful in 1m40s
Documentation Validation / validate-docs (push) Failing after 12s
Documentation Validation / validate-policies-strict (push) Successful in 4s
Integration Tests / test-service-integration (push) Successful in 2m42s
Package Tests / Python package - aitbc-agent-sdk (push) Failing after 34s
Package Tests / Python package - aitbc-core (push) Successful in 27s
Package Tests / Python package - aitbc-crypto (push) Successful in 13s
Package Tests / Python package - aitbc-sdk (push) Successful in 16s
Package Tests / JavaScript package - aitbc-sdk-js (push) Successful in 8s
Package Tests / JavaScript package - aitbc-token (push) Successful in 18s
Python Tests / test-python (push) Failing after 50s
Security Scanning / security-scan (push) Failing after 43s
Multi-Node Stress Testing / stress-test (push) Successful in 12s
Cross-Node Transaction Testing / transaction-test (push) Successful in 9s
Some checks failed
CLI Tests / test-cli (push) Failing after 4s
Deploy to Testnet / deploy-testnet (push) Successful in 1m40s
Documentation Validation / validate-docs (push) Failing after 12s
Documentation Validation / validate-policies-strict (push) Successful in 4s
Integration Tests / test-service-integration (push) Successful in 2m42s
Package Tests / Python package - aitbc-agent-sdk (push) Failing after 34s
Package Tests / Python package - aitbc-core (push) Successful in 27s
Package Tests / Python package - aitbc-crypto (push) Successful in 13s
Package Tests / Python package - aitbc-sdk (push) Successful in 16s
Package Tests / JavaScript package - aitbc-sdk-js (push) Successful in 8s
Package Tests / JavaScript package - aitbc-token (push) Successful in 18s
Python Tests / test-python (push) Failing after 50s
Security Scanning / security-scan (push) Failing after 43s
Multi-Node Stress Testing / stress-test (push) Successful in 12s
Cross-Node Transaction Testing / transaction-test (push) Successful in 9s
- Created aitbc/_version.py with centralized version definition - Updated aitbc/__init__.py to import __version__ from _version module - Updated constants.py to use __version__ for PACKAGE_VERSION - Replaced print() calls with logger in decorators.py, events.py, queue_manager.py, and state.py - Added logger initialization using get_logger(__name__) in config.py, decorators.py, events.py, queue_manager.py, and state.py - Added cli/commands
This commit is contained in:
158
cli/aitbc_cli/commands/hermes.py
Normal file
158
cli/aitbc_cli/commands/hermes.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Hermes training commands for AITBC CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import subprocess
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
|
||||
|
||||
@click.group()
|
||||
def hermes():
|
||||
"""Hermes training operations commands"""
|
||||
pass
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.option('--agent-id', required=True, help='Agent ID')
|
||||
@click.option('--training-type', required=True, help='Type of training')
|
||||
@click.option('--dataset', help='Dataset to use')
|
||||
@click.option('--epochs', type=int, default=100, help='Number of training epochs')
|
||||
@click.option('--batch-size', type=int, default=32, help='Batch size')
|
||||
@click.option('--training-data', help='Path to training data JSON file')
|
||||
@click.option('--stage', help='Training stage')
|
||||
def train(agent_id: str, training_type: str, dataset: Optional[str], epochs: int, batch_size: int, training_data: Optional[str], stage: Optional[str]):
|
||||
"""Start Hermes training for an agent"""
|
||||
if training_data:
|
||||
if not os.path.exists(training_data):
|
||||
error(f"Training data file not found: {training_data}")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(training_data, 'r') as f:
|
||||
training_config = json.load(f)
|
||||
|
||||
# Validate training data matches stage
|
||||
if stage and training_config.get('stage') != stage:
|
||||
error(f"Training data stage mismatch: expected {stage}, got {training_config.get('stage')}")
|
||||
return
|
||||
|
||||
# Initialize logging
|
||||
log_dir = "/var/log/aitbc/agent-training"
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_file = f"{log_dir}/agent_{agent_id}_{stage}_{int(time.time())}.log"
|
||||
|
||||
# Execute training operations
|
||||
operations = training_config.get('training_data', {}).get('operations', [])
|
||||
completed_ops = 0
|
||||
failed_ops = 0
|
||||
|
||||
success(f"Starting training for agent {agent_id}")
|
||||
success(f"Operations to execute: {len(operations)}")
|
||||
|
||||
for i, op in enumerate(operations, 1):
|
||||
operation = op.get('operation')
|
||||
parameters = op.get('parameters', {})
|
||||
|
||||
log_entry = {
|
||||
"timestamp": datetime.datetime.now().isoformat(),
|
||||
"agent_id": agent_id,
|
||||
"stage": stage,
|
||||
"operation": operation,
|
||||
"prompt": {
|
||||
"parameters": parameters,
|
||||
"expected_result": op.get('expected_result')
|
||||
}
|
||||
}
|
||||
|
||||
# Execute training via hermes agent
|
||||
start_time = time.time()
|
||||
try:
|
||||
prompt_message = f"Execute AITBC CLI command: {operation}"
|
||||
if parameters:
|
||||
prompt_message += f" with parameters: {json.dumps(parameters)}"
|
||||
|
||||
cmd = ["hermes", "agent", "--message", prompt_message, "--agent", "main"]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
if result.returncode == 0:
|
||||
reply = {
|
||||
"status": "completed",
|
||||
"result": result.stdout.strip() if result.stdout else "Command executed successfully",
|
||||
"cli_output": result.stdout.strip()
|
||||
}
|
||||
log_entry["status"] = "completed"
|
||||
completed_ops += 1
|
||||
success(f"Operation {i}/{len(operations)}: {operation} - completed ({duration_ms}ms)")
|
||||
else:
|
||||
reply = {
|
||||
"status": "error",
|
||||
"error": result.stderr.strip() if result.stderr else "Command failed",
|
||||
"cli_output": result.stdout.strip(),
|
||||
"cli_error": result.stderr.strip()
|
||||
}
|
||||
log_entry["status"] = "failed"
|
||||
failed_ops += 1
|
||||
error(f"Operation {i}/{len(operations)}: {operation} - failed")
|
||||
|
||||
log_entry["reply"] = reply
|
||||
log_entry["duration_ms"] = duration_ms
|
||||
|
||||
# Write log entry
|
||||
with open(log_file, 'a') as f:
|
||||
f.write(json.dumps(log_entry) + "\n")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
reply = {
|
||||
"status": "error",
|
||||
"error": "Command timed out after 30 seconds"
|
||||
}
|
||||
log_entry["status"] = "failed"
|
||||
log_entry["reply"] = reply
|
||||
log_entry["duration_ms"] = duration_ms
|
||||
failed_ops += 1
|
||||
error(f"Operation {i}/{len(operations)}: {operation} - timed out")
|
||||
|
||||
with open(log_file, 'a') as f:
|
||||
f.write(json.dumps(log_entry) + "\n")
|
||||
except Exception as e:
|
||||
error(f"Operation {i}/{len(operations)}: {operation} - exception: {e}")
|
||||
failed_ops += 1
|
||||
|
||||
success(f"Training completed: {completed_ops}/{len(operations)} successful")
|
||||
success(f"Log file: {log_file}")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error loading training data: {e}")
|
||||
else:
|
||||
success(f"Start {training_type} training for agent {agent_id}")
|
||||
success(f"Epochs: {epochs}, Batch size: {batch_size}")
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.option('--agent-id', help='Agent ID')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def status(agent_id: Optional[str], format: str):
|
||||
"""Get Hermes training status"""
|
||||
success(f"Get Hermes training status for agent {agent_id}")
|
||||
# TODO: Implement actual status check from coordinator API
|
||||
|
||||
|
||||
@hermes.command()
|
||||
@click.option('--agent-id', help='Agent ID')
|
||||
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
|
||||
106
cli/aitbc_cli/commands/mining.py
Normal file
106
cli/aitbc_cli/commands/mining.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Mining commands for AITBC CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from aitbc import AITBCHTTPClient, NetworkError, KEYSTORE_DIR
|
||||
|
||||
DEFAULT_RPC_URL = "http://localhost:8006"
|
||||
DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR
|
||||
|
||||
|
||||
@click.group()
|
||||
def mining():
|
||||
"""Mining operations commands"""
|
||||
pass
|
||||
|
||||
|
||||
@mining.command()
|
||||
@click.argument('wallet_name')
|
||||
@click.option('--threads', type=int, default=1, help='Number of mining threads')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def start(wallet_name: str, threads: int, rpc_url: Optional[str]):
|
||||
"""Start mining with specified wallet"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
# Get wallet address
|
||||
keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet_name}.json"
|
||||
if not keystore_path.exists():
|
||||
error(f"Wallet '{wallet_name}' not found")
|
||||
return False
|
||||
|
||||
with open(keystore_path) as f:
|
||||
wallet_data = json.load(f)
|
||||
address = wallet_data['address']
|
||||
|
||||
# Start mining via RPC
|
||||
mining_config = {
|
||||
"miner_address": address,
|
||||
"threads": threads,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.post("/rpc/mining/start", json=mining_config)
|
||||
success(f"Mining started with wallet '{wallet_name}'")
|
||||
click.echo(f"Miner address: {address}")
|
||||
click.echo(f"Threads: {threads}")
|
||||
click.echo(f"Status: {result.get('status', 'started')}")
|
||||
return result
|
||||
except NetworkError as e:
|
||||
error(f"Error starting mining: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@mining.command()
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def stop(rpc_url: Optional[str]):
|
||||
"""Stop mining"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.post("/rpc/mining/stop")
|
||||
success("Mining stopped")
|
||||
click.echo(f"Status: {result.get('status', 'stopped')}")
|
||||
return True
|
||||
except NetworkError as e:
|
||||
error(f"Error stopping mining: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@mining.command()
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def status(rpc_url: Optional[str]):
|
||||
"""Get mining status"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.get("/rpc/mining/status")
|
||||
success("Mining status:")
|
||||
click.echo(json.dumps(result, indent=2))
|
||||
except NetworkError as e:
|
||||
error(f"Error getting mining status: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
359
cli/aitbc_cli/commands/operations.py
Normal file
359
cli/aitbc_cli/commands/operations.py
Normal file
@@ -0,0 +1,359 @@
|
||||
"""
|
||||
General operations commands for AITBC CLI (marketplace, AI, agents)
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from ..utils.wallet import decrypt_private_key
|
||||
from aitbc import AITBCHTTPClient, NetworkError, KEYSTORE_DIR
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
DEFAULT_RPC_URL = "http://localhost:8006"
|
||||
DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR
|
||||
|
||||
|
||||
@click.group()
|
||||
def operations():
|
||||
"""General operations commands"""
|
||||
pass
|
||||
|
||||
|
||||
# Marketplace operations
|
||||
@operations.group()
|
||||
def marketplace():
|
||||
"""Marketplace operations"""
|
||||
pass
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def list_listings(format: str):
|
||||
"""List marketplace listings"""
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:8102", timeout=30)
|
||||
data = http_client.get("/rpc/marketplace/listings")
|
||||
listings = data.get("listings", [])
|
||||
success(f"Marketplace listings: {len(listings)}")
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(listings, indent=2))
|
||||
else:
|
||||
for listing in listings:
|
||||
click.echo(f" - {listing.get('name', 'unknown')}: {listing.get('price', 0)} AIT")
|
||||
except NetworkError as e:
|
||||
error(f"Error getting marketplace listings: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('listing_id')
|
||||
@click.option('--quantity', type=int, default=1, help='Quantity to purchase')
|
||||
@click.option('--wallet', help='Wallet name for payment')
|
||||
def purchase(listing_id: str, quantity: int, wallet: Optional[str]):
|
||||
"""Purchase from marketplace listing"""
|
||||
success(f"Purchase {quantity} of listing {listing_id}")
|
||||
# TODO: Implement actual purchase logic with wallet signing
|
||||
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--wallet-name', required=True, help='Seller wallet name')
|
||||
@click.option('--item-type', required=True, help='Type of item')
|
||||
@click.option('--price', type=float, required=True, help='Listing price')
|
||||
@click.option('--description', help='Item description')
|
||||
def create_listing(wallet_name: str, item_type: str, price: float, description: Optional[str]):
|
||||
"""Create a marketplace listing"""
|
||||
try:
|
||||
# Get wallet address
|
||||
keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet_name}.json"
|
||||
if not keystore_path.exists():
|
||||
error(f"Wallet '{wallet_name}' not found")
|
||||
return None
|
||||
|
||||
with open(keystore_path) as f:
|
||||
wallet_data = json.load(f)
|
||||
address = wallet_data['address']
|
||||
|
||||
# Create listing via RPC
|
||||
listing_config = {
|
||||
"seller_address": address,
|
||||
"item_type": item_type,
|
||||
"price": price,
|
||||
"description": description or ""
|
||||
}
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:8102", timeout=30)
|
||||
result = http_client.post("/rpc/marketplace/create", json=listing_config)
|
||||
success(f"Listing created successfully")
|
||||
click.echo(f"Item: {item_type}")
|
||||
click.echo(f"Price: {price} AIT")
|
||||
click.echo(f"Listing ID: {result.get('listing_id', 'unknown')}")
|
||||
return result
|
||||
except NetworkError as e:
|
||||
error(f"Error creating listing: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
# AI operations
|
||||
@operations.group()
|
||||
def ai():
|
||||
"""AI operations"""
|
||||
pass
|
||||
|
||||
|
||||
@ai.command()
|
||||
@click.option('--wallet-name', required=True, help='Client wallet name')
|
||||
@click.option('--job-type', required=True, help='Type of AI job')
|
||||
@click.option('--prompt', required=True, help='AI prompt')
|
||||
@click.option('--payment', type=float, required=True, help='Payment amount')
|
||||
@click.option('--model', help='AI model to use')
|
||||
def submit_job(wallet_name: str, job_type: str, prompt: str, payment: float, model: Optional[str]):
|
||||
"""Submit an AI job"""
|
||||
try:
|
||||
# Get wallet address
|
||||
keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet_name}.json"
|
||||
if not keystore_path.exists():
|
||||
error(f"Wallet '{wallet_name}' not found")
|
||||
return None
|
||||
|
||||
with open(keystore_path) as f:
|
||||
wallet_data = json.load(f)
|
||||
address = wallet_data['address']
|
||||
|
||||
# Submit job via coordinator API
|
||||
job_config = {
|
||||
"client_address": address,
|
||||
"job_type": job_type,
|
||||
"prompt": prompt,
|
||||
"payment": payment,
|
||||
"model": model or "default"
|
||||
}
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:9001", timeout=30)
|
||||
result = http_client.post("/v1/jobs", json=job_config)
|
||||
success(f"AI job submitted successfully")
|
||||
click.echo(f"Job ID: {result.get('job_id', 'unknown')}")
|
||||
click.echo(f"Type: {job_type}")
|
||||
click.echo(f"Payment: {payment} AIT")
|
||||
return result
|
||||
except NetworkError as e:
|
||||
error(f"Error submitting AI job: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@ai.command()
|
||||
@click.option('--job-id', help='Specific job ID')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def status(job_id: Optional[str], format: str):
|
||||
"""Get AI job status"""
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:9001", timeout=30)
|
||||
if job_id:
|
||||
result = http_client.get(f"/v1/jobs/{job_id}")
|
||||
success(f"Job status for {job_id}")
|
||||
else:
|
||||
result = http_client.get("/v1/jobs")
|
||||
success(f"All jobs status")
|
||||
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(result, indent=2))
|
||||
else:
|
||||
if job_id:
|
||||
click.echo(f"Status: {result.get('state', 'unknown')}")
|
||||
click.echo(f"Progress: {result.get('progress', '0%')}")
|
||||
else:
|
||||
for job in result.get('jobs', []):
|
||||
click.echo(f" - {job.get('job_id', 'unknown')}: {job.get('state', 'unknown')}")
|
||||
except NetworkError as e:
|
||||
error(f"Error getting AI job status: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@ai.command()
|
||||
@click.option('--job-id', help='Specific job ID')
|
||||
def cancel(job_id: Optional[str]):
|
||||
"""Cancel an AI job"""
|
||||
if not job_id:
|
||||
error("Job ID is required")
|
||||
return
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:9001", timeout=30)
|
||||
result = http_client.post(f"/v1/jobs/{job_id}/cancel")
|
||||
success(f"AI job {job_id} cancelled")
|
||||
except NetworkError as e:
|
||||
error(f"Error cancelling AI job: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
# Agent operations
|
||||
@operations.group()
|
||||
def agent():
|
||||
"""Agent operations"""
|
||||
pass
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.option('--agent-id', required=True, help='Agent ID')
|
||||
@click.option('--status', type=click.Choice(['active', 'inactive', 'busy', 'offline']), default='active', help='Agent status')
|
||||
def register(agent_id: str, status: str):
|
||||
"""Register an agent"""
|
||||
try:
|
||||
agent_config = {
|
||||
"agent_id": agent_id,
|
||||
"status": status
|
||||
}
|
||||
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:9001", timeout=30)
|
||||
result = http_client.post("/v1/agents/register", json=agent_config)
|
||||
success(f"Agent {agent_id} registered with status {status}")
|
||||
except NetworkError as e:
|
||||
error(f"Error registering agent: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.option('--status', help='Filter by status')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def list(status: Optional[str], format: str):
|
||||
"""List registered agents"""
|
||||
try:
|
||||
import requests
|
||||
coordinator_url = "http://localhost:9001"
|
||||
|
||||
query = {}
|
||||
if status:
|
||||
query["status"] = status
|
||||
|
||||
response = requests.post(f"{coordinator_url}/agents/discover", json=query, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
agents = data.get("agents", [])
|
||||
success(f"Agents: {len(agents)}")
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(agents, indent=2))
|
||||
else:
|
||||
for agent in agents:
|
||||
click.echo(f" - {agent.get('agent_id', 'unknown')}: {agent.get('status', 'unknown')} - {agent.get('agent_type', 'unknown')}")
|
||||
else:
|
||||
error(f"Error listing agents: {response.status_code}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.argument('agent_id')
|
||||
def deregister(agent_id: str):
|
||||
"""Deregister an agent"""
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url="http://localhost:9001", timeout=30)
|
||||
result = http_client.post(f"/v1/agents/{agent_id}/deregister")
|
||||
success(f"Agent {agent_id} deregistered")
|
||||
except NetworkError as e:
|
||||
error(f"Error deregistering agent: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.option('--agent', required=True, help='Recipient agent address')
|
||||
@click.option('--message', required=True, help='Message content')
|
||||
@click.option('--wallet', required=True, help='Wallet name for signing')
|
||||
@click.option('--password', help='Wallet password')
|
||||
@click.option('--password-file', help='File containing wallet password')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def message(agent: str, message: str, wallet: str, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
||||
"""Send message to agent via blockchain transaction"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
# Get password
|
||||
if password_file:
|
||||
with open(password_file) as f:
|
||||
password = f.read().strip()
|
||||
elif not password:
|
||||
import getpass
|
||||
password = getpass.getpass("Enter wallet password: ")
|
||||
|
||||
try:
|
||||
# Decrypt wallet
|
||||
keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet}.json"
|
||||
private_key_hex = decrypt_private_key(keystore_path, password)
|
||||
private_key_bytes = bytes.fromhex(private_key_hex)
|
||||
|
||||
# Get sender address
|
||||
with open(keystore_path) as f:
|
||||
keystore_data = json.load(f)
|
||||
sender_address = keystore_data['address']
|
||||
|
||||
# Create transaction with message as payload
|
||||
priv_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
|
||||
pub_hex = priv_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.Raw,
|
||||
format=serialization.PublicFormat.Raw
|
||||
).hex()
|
||||
|
||||
# Get chain_id
|
||||
from ..utils.chain_id import get_chain_id
|
||||
chain_id = get_chain_id(rpc_url)
|
||||
|
||||
# Get actual nonce
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5)
|
||||
account_data = http_client.get(f"/rpc/account/{sender_address}")
|
||||
actual_nonce = account_data.get("nonce", 0)
|
||||
except Exception:
|
||||
actual_nonce = 0
|
||||
|
||||
tx = {
|
||||
"type": "TRANSFER",
|
||||
"chain_id": chain_id,
|
||||
"from": sender_address,
|
||||
"nonce": actual_nonce,
|
||||
"fee": 10,
|
||||
"payload": {
|
||||
"recipient": agent,
|
||||
"amount": 0,
|
||||
"message": message
|
||||
}
|
||||
}
|
||||
|
||||
# Sign transaction
|
||||
tx_string = json.dumps(tx, sort_keys=True)
|
||||
tx["signature"] = priv_key.sign(tx_string.encode()).hex()
|
||||
tx["public_key"] = pub_hex
|
||||
|
||||
# Submit transaction
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.post("/rpc/transaction", json=tx)
|
||||
success(f"Message sent successfully")
|
||||
click.echo(f"From: {sender_address}")
|
||||
click.echo(f"To: {agent}")
|
||||
click.echo(f"Content: {message}")
|
||||
click.echo(f"TX Hash: {result.get('transaction_hash', 'unknown')}")
|
||||
except Exception as e:
|
||||
error(f"Error sending message: {e}")
|
||||
|
||||
93
cli/aitbc_cli/commands/resource.py
Normal file
93
cli/aitbc_cli/commands/resource.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Resource management commands for AITBC CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
|
||||
|
||||
@click.group()
|
||||
def resource():
|
||||
"""Resource management commands"""
|
||||
pass
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.option('--resource-type', required=True, help='Type of resource (gpu, cpu, storage)')
|
||||
@click.option('--quantity', type=int, required=True, help='Quantity of resources')
|
||||
@click.option('--priority', type=click.Choice(['low', 'medium', 'high']), default='medium', help='Allocation priority')
|
||||
def allocate(resource_type: str, quantity: int, priority: str):
|
||||
"""Allocate resources"""
|
||||
success(f"Allocate {quantity} {resource_type} with {priority} priority")
|
||||
# TODO: Implement actual resource allocation via coordinator API
|
||||
click.echo(f"Allocation ID: alloc_{int(time.time())}")
|
||||
click.echo(f"Status: Allocated")
|
||||
click.echo(f"Cost per hour: 25 AIT")
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.option('--resource-id', help='Specific resource ID')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def list(resource_id: Optional[str], format: str):
|
||||
"""List allocated resources"""
|
||||
success("Allocated resources:")
|
||||
resources = [
|
||||
{"type": "gpu", "allocated": 4, "available": 8, "efficiency": "78.5%"},
|
||||
{"type": "cpu", "allocated": "45.2%", "available": "54.8%", "efficiency": "82.1%"},
|
||||
{"type": "storage", "allocated": "45GB", "available": "55GB", "efficiency": "90.0%"}
|
||||
]
|
||||
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(resources, indent=2))
|
||||
else:
|
||||
for res in resources:
|
||||
click.echo(f" - {res['type'].upper()}: {res['allocated']} allocated, {res['available']} available ({res['efficiency']})")
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource_id')
|
||||
def release(resource_id: str):
|
||||
"""Release allocated resources"""
|
||||
success(f"Release resource {resource_id}")
|
||||
# TODO: Implement actual resource release via coordinator API
|
||||
click.echo("Status: Released")
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def utilization(format: str):
|
||||
"""Get resource utilization metrics"""
|
||||
success("Resource utilization:")
|
||||
metrics = {
|
||||
"cpu_utilization": "45.2%",
|
||||
"memory_usage": "2.1GB / 8GB (26%)",
|
||||
"storage_available": "45GB / 100GB",
|
||||
"network_bandwidth": "120Mbps / 1Gbps",
|
||||
"active_agents": 3,
|
||||
"resource_efficiency": "78.5%"
|
||||
}
|
||||
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(metrics, indent=2))
|
||||
else:
|
||||
for key, value in metrics.items():
|
||||
click.echo(f" {key}: {value}")
|
||||
|
||||
|
||||
@resource.command()
|
||||
@click.option('--target', default='all', help='Optimization target (all, cpu, gpu, memory)')
|
||||
@click.option('--agent-id', help='Specific agent ID')
|
||||
def optimize(target: str, agent_id: Optional[str]):
|
||||
"""Optimize resource allocation"""
|
||||
success(f"Optimize resources for target: {target}")
|
||||
if agent_id:
|
||||
click.echo(f"Agent: {agent_id}")
|
||||
# TODO: Implement actual optimization logic
|
||||
click.echo("Optimization score: 85.2%")
|
||||
click.echo("Improvement: 12.5%")
|
||||
click.echo("Status: Optimized")
|
||||
273
cli/aitbc_cli/commands/transactions.py
Normal file
273
cli/aitbc_cli/commands/transactions.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""
|
||||
Transaction commands for AITBC CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
from ..utils.wallet import decrypt_private_key
|
||||
from aitbc import AITBCHTTPClient, NetworkError, KEYSTORE_DIR, get_logger
|
||||
from aitbc.exceptions import ValidationError
|
||||
from aitbc.utils.validation import validate_address
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
DEFAULT_RPC_URL = "http://localhost:8006"
|
||||
DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR
|
||||
|
||||
|
||||
@click.group()
|
||||
def transactions():
|
||||
"""Transaction management commands"""
|
||||
pass
|
||||
|
||||
|
||||
def _send_transaction_impl(from_wallet: str, to_address: str, amount: float, fee: float,
|
||||
password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
|
||||
rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]:
|
||||
"""Send transaction from one wallet to another"""
|
||||
|
||||
# Validate recipient address
|
||||
try:
|
||||
validate_address(to_address)
|
||||
except ValidationError as e:
|
||||
logger.error(f"Invalid recipient address: {e}")
|
||||
error(f"Invalid recipient address: {e}")
|
||||
return None
|
||||
|
||||
# Validate amount
|
||||
if amount <= 0:
|
||||
logger.error(f"Invalid amount: {amount} must be positive")
|
||||
error("Amount must be positive")
|
||||
return None
|
||||
|
||||
# Ensure keystore_dir is a Path object
|
||||
if keystore_dir is None:
|
||||
keystore_dir = DEFAULT_KEYSTORE_DIR
|
||||
if isinstance(keystore_dir, str):
|
||||
keystore_dir = Path(keystore_dir)
|
||||
|
||||
# Get sender wallet info
|
||||
sender_keystore = keystore_dir / f"{from_wallet}.json"
|
||||
if not sender_keystore.exists():
|
||||
error(f"Wallet '{from_wallet}' not found")
|
||||
return None
|
||||
|
||||
with open(sender_keystore) as f:
|
||||
sender_data = json.load(f)
|
||||
|
||||
sender_address = sender_data['address']
|
||||
|
||||
# Decrypt private key
|
||||
try:
|
||||
private_key_hex = decrypt_private_key(sender_keystore, password)
|
||||
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex))
|
||||
except Exception as e:
|
||||
error(f"Error decrypting wallet: {e}")
|
||||
return None
|
||||
|
||||
# Get chain_id from RPC health endpoint or use override
|
||||
from ..utils.chain_id import get_chain_id
|
||||
chain_id = get_chain_id(rpc_url, override=None, timeout=5)
|
||||
|
||||
# Get actual nonce from blockchain
|
||||
actual_nonce = 0
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5)
|
||||
account_data = http_client.get(f"/rpc/account/{sender_address}")
|
||||
actual_nonce = account_data.get("nonce", 0)
|
||||
except NetworkError:
|
||||
actual_nonce = 0
|
||||
except Exception:
|
||||
actual_nonce = 0
|
||||
|
||||
# Create transaction
|
||||
transaction = {
|
||||
"type": "TRANSFER",
|
||||
"chain_id": chain_id,
|
||||
"from": sender_address,
|
||||
"nonce": actual_nonce,
|
||||
"fee": int(fee),
|
||||
"payload": {
|
||||
"recipient": to_address,
|
||||
"amount": int(amount)
|
||||
}
|
||||
}
|
||||
|
||||
# Sign transaction
|
||||
message = json.dumps(transaction, sort_keys=True).encode()
|
||||
signature = private_key.sign(message)
|
||||
transaction["signature"] = signature.hex()
|
||||
|
||||
# Submit to blockchain
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.post("/rpc/transaction", json=transaction)
|
||||
tx_hash = result.get("transaction_hash")
|
||||
success(f"Transaction submitted: {tx_hash}")
|
||||
logger.info(f"Transaction submitted: {tx_hash} from {from_wallet} to {to_address}")
|
||||
return tx_hash
|
||||
except NetworkError as e:
|
||||
logger.error(f"Network error submitting transaction: {e}")
|
||||
error(f"Error submitting transaction: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error submitting transaction: {e}")
|
||||
error(f"Error: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@transactions.command()
|
||||
@click.option('--from', 'from_wallet', required=True, help='From wallet name')
|
||||
@click.option('--to', 'to_address', required=True, help='To address')
|
||||
@click.option('--amount', type=float, required=True, help='Amount to send')
|
||||
@click.option('--fee', type=float, default=0.001, help='Transaction fee')
|
||||
@click.option('--password', help='Wallet password')
|
||||
@click.option('--password-file', help='File containing wallet password')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def send(from_wallet: str, to_address: str, amount: float, fee: float, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
||||
"""Send transaction from one wallet to another"""
|
||||
if password_file:
|
||||
with open(password_file) as f:
|
||||
password = f.read().strip()
|
||||
elif not password:
|
||||
import getpass
|
||||
password = getpass.getpass("Enter wallet password: ")
|
||||
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
tx_hash = _send_transaction_impl(from_wallet, to_address, amount, fee, password, rpc_url=rpc_url)
|
||||
if tx_hash:
|
||||
success(f"Transaction sent: {tx_hash}")
|
||||
|
||||
|
||||
@transactions.command()
|
||||
@click.option('--transactions-file', required=True, help='JSON file with batch transactions')
|
||||
@click.option('--password', help='Wallet password')
|
||||
@click.option('--password-file', help='File containing wallet password')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def batch(transactions_file: str, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
||||
"""Send batch transactions"""
|
||||
if password_file:
|
||||
with open(password_file) as f:
|
||||
password = f.read().strip()
|
||||
elif not password:
|
||||
import getpass
|
||||
password = getpass.getpass("Enter wallet password: ")
|
||||
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
with open(transactions_file) as f:
|
||||
transactions_data = json.load(f)
|
||||
|
||||
results = []
|
||||
for tx in transactions_data:
|
||||
try:
|
||||
tx_hash = _send_transaction_impl(
|
||||
tx['from_wallet'],
|
||||
tx['to_address'],
|
||||
tx['amount'],
|
||||
tx.get('fee', 10.0),
|
||||
password,
|
||||
rpc_url=rpc_url
|
||||
)
|
||||
results.append({
|
||||
'transaction': tx,
|
||||
'hash': tx_hash,
|
||||
'success': tx_hash is not None
|
||||
})
|
||||
|
||||
if tx_hash:
|
||||
success(f"Transaction sent: {tx['from_wallet']} → {tx['to_address']} ({tx['amount']} AIT)")
|
||||
else:
|
||||
error(f"Transaction failed: {tx['from_wallet']} → {tx['to_address']}")
|
||||
|
||||
except Exception as e:
|
||||
results.append({
|
||||
'transaction': tx,
|
||||
'hash': None,
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
error(f"Transaction error: {e}")
|
||||
|
||||
success(f"Batch completed: {len([r for r in results if r['success']])}/{len(results)} successful")
|
||||
|
||||
|
||||
@transactions.command()
|
||||
@click.argument('tx_hash')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def status(tx_hash: str, rpc_url: Optional[str]):
|
||||
"""Get transaction status"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
result = http_client.get(f"/rpc/transaction/{tx_hash}")
|
||||
success(f"Transaction status for {tx_hash}")
|
||||
click.echo(json.dumps(result, indent=2))
|
||||
except NetworkError as e:
|
||||
error(f"Error getting transaction status: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@transactions.command()
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def pending(rpc_url: Optional[str]):
|
||||
"""Get pending transactions"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30)
|
||||
data = http_client.get("/rpc/pending")
|
||||
transactions = data.get("transactions", [])
|
||||
success(f"Pending transactions: {len(transactions)}")
|
||||
for tx in transactions:
|
||||
click.echo(f" - {tx.get('hash', 'unknown')}: {tx.get('amount', 0)} AIT")
|
||||
except NetworkError as e:
|
||||
error(f"Error getting pending transactions: {e}")
|
||||
except Exception as e:
|
||||
error(f"Error: {e}")
|
||||
|
||||
|
||||
@transactions.command()
|
||||
@click.option('--from', 'from_wallet', required=True, help='From wallet name')
|
||||
@click.option('--to', 'to_address', required=True, help='To address')
|
||||
@click.option('--amount', type=float, required=True, help='Amount to send')
|
||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||
def estimate_fee(from_wallet: str, to_address: str, amount: float, rpc_url: Optional[str]):
|
||||
"""Estimate transaction fee"""
|
||||
if not rpc_url:
|
||||
rpc_url = DEFAULT_RPC_URL
|
||||
|
||||
try:
|
||||
test_tx = {
|
||||
"sender": "",
|
||||
"recipient": to_address,
|
||||
"value": int(amount),
|
||||
"fee": 10,
|
||||
"nonce": 0,
|
||||
"type": "transfer",
|
||||
"payload": {}
|
||||
}
|
||||
|
||||
try:
|
||||
http_client = AITBCHTTPClient(base_url=rpc_url, timeout=10)
|
||||
fee_data = http_client.post("/rpc/estimateFee", json=test_tx)
|
||||
estimated_fee = fee_data.get("estimated_fee", 10.0)
|
||||
success(f"Estimated fee: {estimated_fee} AIT")
|
||||
except NetworkError:
|
||||
success(f"Estimated fee: 10.0 AIT (default)")
|
||||
except Exception as e:
|
||||
error(f"Error estimating fee: {e}")
|
||||
success(f"Estimated fee: 10.0 AIT (default)")
|
||||
73
cli/aitbc_cli/commands/workflow.py
Normal file
73
cli/aitbc_cli/commands/workflow.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Workflow commands for AITBC CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from ..utils import error, success
|
||||
|
||||
|
||||
@click.group()
|
||||
def workflow():
|
||||
"""Workflow management commands"""
|
||||
pass
|
||||
|
||||
|
||||
@workflow.command()
|
||||
@click.argument('workflow_name')
|
||||
@click.option('--config', help='Workflow configuration file')
|
||||
@click.option('--dry-run', is_flag=True, help='Dry run without executing')
|
||||
def run(workflow_name: str, config: Optional[str], dry_run: bool):
|
||||
"""Run a workflow"""
|
||||
if dry_run:
|
||||
success(f"Dry run for workflow {workflow_name}")
|
||||
click.echo("Would execute workflow without making changes")
|
||||
return
|
||||
|
||||
success(f"Run workflow {workflow_name}")
|
||||
if config:
|
||||
click.echo(f"Using config: {config}")
|
||||
|
||||
# TODO: Implement actual workflow execution logic
|
||||
click.echo(f"Execution ID: wf_exec_{int(time.time())}")
|
||||
click.echo("Status: Running")
|
||||
|
||||
|
||||
@workflow.command()
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
def list(format: str):
|
||||
"""List available workflows"""
|
||||
success("Available workflows:")
|
||||
workflows = [
|
||||
{"name": "gpu-marketplace", "status": "active", "steps": 5},
|
||||
{"name": "ai-job-processing", "status": "active", "steps": 3},
|
||||
{"name": "mining-optimization", "status": "inactive", "steps": 4}
|
||||
]
|
||||
|
||||
if format == 'json':
|
||||
click.echo(json.dumps(workflows, indent=2))
|
||||
else:
|
||||
for wf in workflows:
|
||||
click.echo(f" - {wf['name']}: {wf['status']} ({wf['steps']} steps)")
|
||||
|
||||
|
||||
@workflow.command()
|
||||
@click.argument('workflow_name')
|
||||
def status(workflow_name: str):
|
||||
"""Get workflow status"""
|
||||
success(f"Get status for workflow {workflow_name}")
|
||||
# TODO: Implement actual status check from workflow engine
|
||||
click.echo("Status: Not running")
|
||||
click.echo("Last execution: Never")
|
||||
|
||||
|
||||
@workflow.command()
|
||||
@click.argument('workflow_name')
|
||||
def stop(workflow_name: str):
|
||||
"""Stop a running workflow"""
|
||||
success(f"Stop workflow {workflow_name}")
|
||||
# TODO: Implement actual stop command via workflow engine
|
||||
Reference in New Issue
Block a user