From c0952c2525de82f6a1105893cc291b63ff6924bd Mon Sep 17 00:00:00 2001 From: aitbc1 Date: Thu, 26 Mar 2026 09:12:02 +0100 Subject: [PATCH] refactor: flatten CLI directory structure - remove 'box in a box' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../DISABLED_COMMANDS_CLEANUP.md | 0 cli/{aitbc_cli => }/__init__.py | 0 cli/aitbc_cli/commands/blockchain.py.backup | 1187 --------- cli/aitbc_cli/commands/deployment.py | 378 --- cli/aitbc_cli/commands/miner.py.backup | 637 ----- cli/aitbc_cli/commands/wallet.py.backup | 2229 ----------------- cli/{aitbc_cli => }/auth/__init__.py | 2 +- cli/{aitbc_cli => }/commands/__init__.py | 0 cli/{aitbc_cli => }/commands/admin.py | 4 +- .../commands/advanced_analytics.py | 2 +- cli/{aitbc_cli => }/commands/agent.py | 2 +- cli/{aitbc_cli => }/commands/agent_comm.py | 6 +- cli/{aitbc_cli => }/commands/ai.py | 0 .../commands/ai_surveillance.py | 2 +- cli/{aitbc_cli => }/commands/ai_trading.py | 2 +- cli/{aitbc_cli => }/commands/analytics.py | 6 +- cli/{aitbc_cli => }/commands/auth.py | 4 +- cli/{aitbc_cli => }/commands/blockchain.py | 6 +- cli/{aitbc_cli => }/commands/chain.py | 10 +- cli/{aitbc_cli => }/commands/client.py | 4 +- cli/{aitbc_cli => }/commands/compliance.py | 2 +- cli/{aitbc_cli => }/commands/config.py | 8 +- cli/{aitbc_cli => }/commands/cross_chain.py | 4 +- cli/{aitbc_cli => }/commands/dao.py | 4 +- cli/commands/deployment.py | 91 + .../commands/enterprise_integration.py | 2 +- cli/{aitbc_cli => }/commands/exchange.py | 4 +- cli/{aitbc_cli => }/commands/explorer.py | 2 +- cli/{aitbc_cli => }/commands/genesis.py | 12 +- .../commands/genesis_protection.py | 2 +- .../commands/global_ai_agents.py | 2 +- .../commands/global_infrastructure.py | 2 +- cli/{aitbc_cli => }/commands/governance.py | 2 +- cli/{aitbc_cli => }/commands/keystore.py | 0 cli/{aitbc_cli => }/commands/market_maker.py | 2 +- cli/{aitbc_cli => }/commands/marketplace.py | 2 +- .../commands/marketplace_advanced.py | 2 +- .../commands/marketplace_cmd.py | 6 +- cli/{aitbc_cli => }/commands/miner.py | 2 +- cli/{aitbc_cli => }/commands/monitor.py | 2 +- .../commands/multi_region_load_balancer.py | 2 +- cli/{aitbc_cli => }/commands/multimodal.py | 2 +- cli/{aitbc_cli => }/commands/multisig.py | 2 +- cli/{aitbc_cli => }/commands/node.py | 10 +- cli/{aitbc_cli => }/commands/openclaw.py | 2 +- cli/{aitbc_cli => }/commands/optimize.py | 2 +- cli/{aitbc_cli => }/commands/oracle.py | 2 +- .../commands/performance_test.py | 0 .../commands/plugin_analytics.py | 2 +- .../commands/plugin_marketplace.py | 2 +- .../commands/plugin_registry.py | 2 +- .../commands/plugin_security.py | 2 +- .../commands/production_deploy.py | 0 cli/{aitbc_cli => }/commands/regulatory.py | 2 +- cli/{aitbc_cli => }/commands/security_test.py | 0 cli/{aitbc_cli => }/commands/simulate.py | 2 +- cli/{aitbc_cli => }/commands/surveillance.py | 2 +- cli/{aitbc_cli => }/commands/swarm.py | 2 +- cli/{aitbc_cli => }/commands/sync.py | 2 +- cli/{aitbc_cli => }/commands/test_cli.py | 12 +- .../commands/transfer_control.py | 2 +- cli/{aitbc_cli => }/commands/wallet.py | 58 +- cli/{aitbc_cli => }/config/__init__.py | 0 cli/{aitbc_cli => }/core/__init__.py | 0 .../core/agent_communication.py | 4 +- cli/{aitbc_cli => }/core/analytics.py | 6 +- cli/{aitbc_cli => }/core/chain_manager.py | 2 +- cli/{aitbc_cli => }/core/config.py | 0 cli/{aitbc_cli => }/core/genesis_generator.py | 4 +- cli/{aitbc_cli => }/core/marketplace.py | 4 +- cli/{aitbc_cli => }/core/node_client.py | 8 +- .../dual_mode_wallet_adapter.py | 0 cli/{aitbc_cli => }/imports.py | 0 cli/{aitbc_cli => }/kyc_aml_providers.py | 0 cli/{aitbc_cli => }/main.py | 92 +- cli/{aitbc_cli => }/main_minimal.py | 0 cli/{aitbc_cli => }/models/__init__.py | 0 cli/{aitbc_cli => }/models/chain.py | 0 cli/{aitbc_cli => }/plugins.py | 0 cli/{aitbc_cli => }/security/__init__.py | 0 .../security/translation_policy.py | 0 cli/setup.py | 4 +- cli/{aitbc_cli => }/utils/__init__.py | 0 cli/{aitbc_cli => }/utils/crypto_utils.py | 0 cli/{aitbc_cli => }/utils/secure_audit.py | 0 cli/{aitbc_cli => }/utils/security.py | 0 cli/{aitbc_cli => }/utils/subprocess.py | 0 cli/{aitbc_cli => }/wallet_daemon_client.py | 0 .../wallet_migration_service.py | 0 89 files changed, 265 insertions(+), 4605 deletions(-) rename cli/{aitbc_cli => }/DISABLED_COMMANDS_CLEANUP.md (100%) rename cli/{aitbc_cli => }/__init__.py (100%) delete mode 100755 cli/aitbc_cli/commands/blockchain.py.backup delete mode 100755 cli/aitbc_cli/commands/deployment.py delete mode 100755 cli/aitbc_cli/commands/miner.py.backup delete mode 100755 cli/aitbc_cli/commands/wallet.py.backup rename cli/{aitbc_cli => }/auth/__init__.py (98%) rename cli/{aitbc_cli => }/commands/__init__.py (100%) rename cli/{aitbc_cli => }/commands/admin.py (99%) rename cli/{aitbc_cli => }/commands/advanced_analytics.py (99%) rename cli/{aitbc_cli => }/commands/agent.py (99%) rename cli/{aitbc_cli => }/commands/agent_comm.py (99%) rename cli/{aitbc_cli => }/commands/ai.py (100%) rename cli/{aitbc_cli => }/commands/ai_surveillance.py (99%) rename cli/{aitbc_cli => }/commands/ai_trading.py (99%) rename cli/{aitbc_cli => }/commands/analytics.py (99%) rename cli/{aitbc_cli => }/commands/auth.py (98%) rename cli/{aitbc_cli => }/commands/blockchain.py (99%) rename cli/{aitbc_cli => }/commands/chain.py (98%) rename cli/{aitbc_cli => }/commands/client.py (99%) rename cli/{aitbc_cli => }/commands/compliance.py (99%) rename cli/{aitbc_cli => }/commands/config.py (98%) rename cli/{aitbc_cli => }/commands/cross_chain.py (99%) rename cli/{aitbc_cli => }/commands/dao.py (99%) create mode 100644 cli/commands/deployment.py rename cli/{aitbc_cli => }/commands/enterprise_integration.py (99%) rename cli/{aitbc_cli => }/commands/exchange.py (99%) rename cli/{aitbc_cli => }/commands/explorer.py (99%) rename cli/{aitbc_cli => }/commands/genesis.py (98%) rename cli/{aitbc_cli => }/commands/genesis_protection.py (99%) rename cli/{aitbc_cli => }/commands/global_ai_agents.py (98%) rename cli/{aitbc_cli => }/commands/global_infrastructure.py (99%) rename cli/{aitbc_cli => }/commands/governance.py (99%) rename cli/{aitbc_cli => }/commands/keystore.py (100%) rename cli/{aitbc_cli => }/commands/market_maker.py (99%) rename cli/{aitbc_cli => }/commands/marketplace.py (99%) rename cli/{aitbc_cli => }/commands/marketplace_advanced.py (99%) rename cli/{aitbc_cli => }/commands/marketplace_cmd.py (99%) rename cli/{aitbc_cli => }/commands/miner.py (99%) rename cli/{aitbc_cli => }/commands/monitor.py (99%) rename cli/{aitbc_cli => }/commands/multi_region_load_balancer.py (98%) rename cli/{aitbc_cli => }/commands/multimodal.py (99%) rename cli/{aitbc_cli => }/commands/multisig.py (99%) rename cli/{aitbc_cli => }/commands/node.py (97%) rename cli/{aitbc_cli => }/commands/openclaw.py (99%) rename cli/{aitbc_cli => }/commands/optimize.py (99%) rename cli/{aitbc_cli => }/commands/oracle.py (99%) rename cli/{aitbc_cli => }/commands/performance_test.py (100%) rename cli/{aitbc_cli => }/commands/plugin_analytics.py (98%) rename cli/{aitbc_cli => }/commands/plugin_marketplace.py (99%) rename cli/{aitbc_cli => }/commands/plugin_registry.py (99%) rename cli/{aitbc_cli => }/commands/plugin_security.py (98%) rename cli/{aitbc_cli => }/commands/production_deploy.py (100%) rename cli/{aitbc_cli => }/commands/regulatory.py (99%) rename cli/{aitbc_cli => }/commands/security_test.py (100%) rename cli/{aitbc_cli => }/commands/simulate.py (99%) rename cli/{aitbc_cli => }/commands/surveillance.py (99%) rename cli/{aitbc_cli => }/commands/swarm.py (99%) rename cli/{aitbc_cli => }/commands/sync.py (97%) rename cli/{aitbc_cli => }/commands/test_cli.py (98%) rename cli/{aitbc_cli => }/commands/transfer_control.py (99%) rename cli/{aitbc_cli => }/commands/wallet.py (98%) rename cli/{aitbc_cli => }/config/__init__.py (100%) rename cli/{aitbc_cli => }/core/__init__.py (100%) rename cli/{aitbc_cli => }/core/agent_communication.py (99%) rename cli/{aitbc_cli => }/core/analytics.py (99%) rename cli/{aitbc_cli => }/core/chain_manager.py (99%) rename cli/{aitbc_cli => }/core/config.py (100%) rename cli/{aitbc_cli => }/core/genesis_generator.py (99%) rename cli/{aitbc_cli => }/core/marketplace.py (99%) rename cli/{aitbc_cli => }/core/node_client.py (98%) rename cli/{aitbc_cli => }/dual_mode_wallet_adapter.py (100%) rename cli/{aitbc_cli => }/imports.py (100%) rename cli/{aitbc_cli => }/kyc_aml_providers.py (100%) rename cli/{aitbc_cli => }/main.py (79%) rename cli/{aitbc_cli => }/main_minimal.py (100%) rename cli/{aitbc_cli => }/models/__init__.py (100%) rename cli/{aitbc_cli => }/models/chain.py (100%) rename cli/{aitbc_cli => }/plugins.py (100%) rename cli/{aitbc_cli => }/security/__init__.py (100%) rename cli/{aitbc_cli => }/security/translation_policy.py (100%) rename cli/{aitbc_cli => }/utils/__init__.py (100%) rename cli/{aitbc_cli => }/utils/crypto_utils.py (100%) rename cli/{aitbc_cli => }/utils/secure_audit.py (100%) rename cli/{aitbc_cli => }/utils/security.py (100%) rename cli/{aitbc_cli => }/utils/subprocess.py (100%) rename cli/{aitbc_cli => }/wallet_daemon_client.py (100%) rename cli/{aitbc_cli => }/wallet_migration_service.py (100%) diff --git a/cli/aitbc_cli/DISABLED_COMMANDS_CLEANUP.md b/cli/DISABLED_COMMANDS_CLEANUP.md similarity index 100% rename from cli/aitbc_cli/DISABLED_COMMANDS_CLEANUP.md rename to cli/DISABLED_COMMANDS_CLEANUP.md diff --git a/cli/aitbc_cli/__init__.py b/cli/__init__.py similarity index 100% rename from cli/aitbc_cli/__init__.py rename to cli/__init__.py diff --git a/cli/aitbc_cli/commands/blockchain.py.backup b/cli/aitbc_cli/commands/blockchain.py.backup deleted file mode 100755 index 3306746a..00000000 --- a/cli/aitbc_cli/commands/blockchain.py.backup +++ /dev/null @@ -1,1187 +0,0 @@ -"""Blockchain commands for AITBC CLI""" - -import click -import httpx - -def _get_node_endpoint(ctx): - try: - config = ctx.obj['config'] - # Use the new blockchain_rpc_url from config - return config.blockchain_rpc_url - except: - return "http://127.0.0.1:8006" # Use new blockchain RPC port - -from typing import Optional, List -from ..utils import output, error - - -@click.group() -@click.pass_context -def blockchain(ctx): - """Query blockchain information and status""" - # Set role for blockchain commands - ctx.ensure_object(dict) - ctx.parent.detected_role = 'blockchain' - - -@blockchain.command() -@click.option("--limit", type=int, default=10, help="Number of blocks to show") -@click.option("--from-height", type=int, help="Start from this block height") -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Query blocks across all available chains') -@click.pass_context -def blocks(ctx, limit: int, from_height: Optional[int], chain_id: str, all_chains: bool): - """List recent blocks across chains""" - try: - config = ctx.obj['config'] - - if all_chains: - # Query all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_blocks = {} - - for chain in chains: - try: - node_url = _get_node_endpoint(ctx) - - # Get blocks from the specific chain - with httpx.Client() as client: - if from_height: - # Get blocks range - response = client.get( - f"{node_url}/rpc/blocks-range", - params={"from_height": from_height, "limit": limit, "chain_id": chain}, - timeout=5 - ) - else: - # Get recent blocks starting from head - response = client.get( - f"{node_url}/rpc/blocks-range", - params={"limit": limit, "chain_id": chain}, - timeout=5 - ) - - if response.status_code == 200: - all_blocks[chain] = response.json() - else: - # Fallback to getting head block for this chain - head_response = client.get(f"{node_url}/rpc/head?chain_id={chain}", timeout=5) - if head_response.status_code == 200: - head_data = head_response.json() - all_blocks[chain] = { - "blocks": [head_data], - "message": f"Showing head block only for chain {chain} (height {head_data.get('height', 'unknown')})" - } - else: - all_blocks[chain] = {"error": f"Failed to get blocks: HTTP {response.status_code}"} - except Exception as e: - all_blocks[chain] = {"error": str(e)} - - output({ - "chains": all_blocks, - "total_chains": len(chains), - "successful_queries": sum(1 for b in all_blocks.values() if "error" not in b), - "limit": limit, - "from_height": from_height, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - node_url = _get_node_endpoint(ctx) - - # Get blocks from the local blockchain node - with httpx.Client() as client: - if from_height: - # Get blocks range - response = client.get( - f"{node_url}/rpc/blocks-range", - params={"from_height": from_height, "limit": limit, "chain_id": target_chain}, - timeout=5 - ) - else: - # Get recent blocks starting from head - response = client.get( - f"{node_url}/rpc/blocks-range", - params={"limit": limit, "chain_id": target_chain}, - timeout=5 - ) - - if response.status_code == 200: - blocks_data = response.json() - output({ - "blocks": blocks_data, - "chain_id": target_chain, - "limit": limit, - "from_height": from_height, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - # Fallback to getting head block if range not available - head_response = client.get(f"{node_url}/rpc/head?chain_id={target_chain}", timeout=5) - if head_response.status_code == 200: - head_data = head_response.json() - output({ - "blocks": [head_data], - "chain_id": target_chain, - "message": f"Showing head block only for chain {target_chain} (height {head_data.get('height', 'unknown')})", - "query_type": "single_chain_fallback" - }, ctx.obj['output_format']) - else: - error(f"Failed to get blocks: {response.status_code} - {response.text}") - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.argument("block_hash") -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Search block across all available chains') -@click.pass_context -def block(ctx, block_hash: str, chain_id: str, all_chains: bool): - """Get details of a specific block across chains""" - try: - config = ctx.obj['config'] - - if all_chains: - # Search for block across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - block_results = {} - - for chain in chains: - try: - node_url = _get_node_endpoint(ctx) - - with httpx.Client() as client: - # First try to get block by hash - response = client.get( - f"{node_url}/rpc/blocks/by_hash/{block_hash}?chain_id={chain}", - timeout=5 - ) - - if response.status_code == 200: - block_results[chain] = response.json() - else: - # If by_hash not available, try to get by height (if hash looks like a number) - try: - height = int(block_hash) - height_response = client.get(f"{node_url}/rpc/blocks/{height}?chain_id={chain}", timeout=5) - if height_response.status_code == 200: - block_results[chain] = height_response.json() - else: - block_results[chain] = {"error": f"Block not found: HTTP {height_response.status_code}"} - except ValueError: - block_results[chain] = {"error": f"Block not found: HTTP {response.status_code}"} - - except Exception as e: - block_results[chain] = {"error": str(e)} - - # Count successful searches - successful_searches = sum(1 for result in block_results.values() if "error" not in result) - - output({ - "block_hash": block_hash, - "chains": block_results, - "total_chains": len(chains), - "successful_searches": successful_searches, - "query_type": "all_chains", - "found_in_chains": [chain for chain, result in block_results.items() if "error" not in result] - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - node_url = _get_node_endpoint(ctx) - - with httpx.Client() as client: - # First try to get block by hash - response = client.get( - f"{node_url}/rpc/blocks/by_hash/{block_hash}?chain_id={target_chain}", - timeout=5 - ) - - if response.status_code == 200: - block_data = response.json() - output({ - "block_data": block_data, - "chain_id": target_chain, - "block_hash": block_hash, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - # If by_hash not available, try to get by height (if hash looks like a number) - try: - height = int(block_hash) - height_response = client.get(f"{node_url}/rpc/blocks/{height}?chain_id={target_chain}", timeout=5) - if height_response.status_code == 200: - block_data = height_response.json() - output({ - "block_data": block_data, - "chain_id": target_chain, - "block_hash": block_hash, - "height": height, - "query_type": "single_chain_by_height" - }, ctx.obj['output_format']) - else: - error(f"Block not found in chain {target_chain}: {height_response.status_code}") - except ValueError: - error(f"Block not found in chain {target_chain}: {response.status_code}") - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.argument("tx_hash") -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Search transaction across all available chains') -@click.pass_context -def transaction(ctx, tx_hash: str, chain_id: str, all_chains: bool): - """Get transaction details across chains""" - config = ctx.obj['config'] - - try: - if all_chains: - # Search for transaction across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - tx_results = {} - - for chain in chains: - try: - with httpx.Client() as client: - response = client.get( - f"{config.coordinator_url}/explorer/transactions/{tx_hash}?chain_id={chain}", - headers={"X-Api-Key": config.api_key or ""}, - timeout=5 - ) - - if response.status_code == 200: - tx_results[chain] = response.json() - else: - tx_results[chain] = {"error": f"Transaction not found: HTTP {response.status_code}"} - - except Exception as e: - tx_results[chain] = {"error": str(e)} - - # Count successful searches - successful_searches = sum(1 for result in tx_results.values() if "error" not in result) - - output({ - "tx_hash": tx_hash, - "chains": tx_results, - "total_chains": len(chains), - "successful_searches": successful_searches, - "query_type": "all_chains", - "found_in_chains": [chain for chain, result in tx_results.items() if "error" not in result] - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - response = client.get( - f"{config.coordinator_url}/explorer/transactions/{tx_hash}?chain_id={target_chain}", - headers={"X-Api-Key": config.api_key or ""}, - timeout=5 - ) - - if response.status_code == 200: - tx_data = response.json() - output({ - "tx_data": tx_data, - "chain_id": target_chain, - "tx_hash": tx_hash, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - error(f"Transaction not found in chain {target_chain}: {response.status_code}") - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option("--node", type=int, default=1, help="Node number (1, 2, or 3)") -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get status across all available chains') -@click.pass_context -def status(ctx, node: int, chain_id: str, all_chains: bool): - """Get blockchain node status across chains""" - config = ctx.obj['config'] - - # Map node to RPC URL using new port logic - node_urls = { - 1: "http://localhost:8006", # Primary Blockchain RPC - 2: "http://localhost:8026", # Development Blockchain RPC - 3: "http://aitbc.keisanki.net/rpc" - } - - rpc_url = node_urls.get(node) - if not rpc_url: - error(f"Invalid node number: {node}") - return - - try: - if all_chains: - # Get status across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_status = {} - - for chain in chains: - try: - with httpx.Client() as client: - # Use health endpoint with chain context - health_url = f"{rpc_url}/health?chain_id={chain}" - response = client.get(health_url, timeout=5) - - if response.status_code == 200: - status_data = response.json() - all_status[chain] = { - "node": node, - "rpc_url": rpc_url, - "chain_id": chain, - "status": status_data, - "healthy": True - } - else: - all_status[chain] = { - "node": node, - "rpc_url": rpc_url, - "chain_id": chain, - "error": f"HTTP {response.status_code}", - "healthy": False - } - except Exception as e: - all_status[chain] = { - "node": node, - "rpc_url": rpc_url, - "chain_id": chain, - "error": str(e), - "healthy": False - } - - # Count healthy chains - healthy_chains = sum(1 for status in all_status.values() if status.get("healthy", False)) - - output({ - "node": node, - "rpc_url": rpc_url, - "chains": all_status, - "total_chains": len(chains), - "healthy_chains": healthy_chains, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - # Use health endpoint with chain context - health_url = f"{rpc_url}/health?chain_id={target_chain}" - response = client.get(health_url, timeout=5) - - if response.status_code == 200: - status_data = response.json() - output({ - "node": node, - "rpc_url": rpc_url, - "chain_id": target_chain, - "status": status_data, - "healthy": True, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - output({ - "node": node, - "rpc_url": rpc_url, - "chain_id": target_chain, - "error": f"HTTP {response.status_code}", - "healthy": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Failed to connect to node {node}: {e}") - - -@blockchain.command() -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get sync status across all available chains') -@click.pass_context -def sync_status(ctx, chain_id: str, all_chains: bool): - """Get blockchain synchronization status across chains""" - config = ctx.obj['config'] - - try: - if all_chains: - # Get sync status across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_sync_status = {} - - for chain in chains: - try: - with httpx.Client() as client: - response = client.get( - f"{config.coordinator_url}/v1/sync-status?chain_id={chain}", - headers={"X-Api-Key": config.api_key or ""}, - timeout=5 - ) - - if response.status_code == 200: - sync_data = response.json() - all_sync_status[chain] = { - "chain_id": chain, - "sync_status": sync_data, - "available": True - } - else: - all_sync_status[chain] = { - "chain_id": chain, - "error": f"HTTP {response.status_code}", - "available": False - } - except Exception as e: - all_sync_status[chain] = { - "chain_id": chain, - "error": str(e), - "available": False - } - - # Count available chains - available_chains = sum(1 for status in all_sync_status.values() if status.get("available", False)) - - output({ - "chains": all_sync_status, - "total_chains": len(chains), - "available_chains": available_chains, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - response = client.get( - f"{config.coordinator_url}/v1/sync-status?chain_id={target_chain}", - headers={"X-Api-Key": config.api_key or ""}, - timeout=5 - ) - - if response.status_code == 200: - sync_data = response.json() - output({ - "chain_id": target_chain, - "sync_status": sync_data, - "available": True, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - output({ - "chain_id": target_chain, - "error": f"HTTP {response.status_code}", - "available": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get peers across all available chains') -@click.pass_context -def peers(ctx, chain_id: str, all_chains: bool): - """List connected peers across chains""" - try: - config = ctx.obj['config'] - node_url = _get_node_endpoint(ctx) - - if all_chains: - # Get peers across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_peers = {} - - for chain in chains: - try: - with httpx.Client() as client: - # Try to get peers from the local blockchain node with chain context - response = client.get( - f"{node_url}/rpc/peers?chain_id={chain}", - timeout=5 - ) - - if response.status_code == 200: - peers_data = response.json() - all_peers[chain] = { - "chain_id": chain, - "peers": peers_data.get("peers", peers_data), - "available": True - } - else: - all_peers[chain] = { - "chain_id": chain, - "peers": [], - "message": "No P2P peers available - node running in RPC-only mode", - "available": False - } - except Exception as e: - all_peers[chain] = { - "chain_id": chain, - "peers": [], - "error": str(e), - "available": False - } - - # Count chains with available peers - chains_with_peers = sum(1 for peers in all_peers.values() if peers.get("available", False)) - - output({ - "chains": all_peers, - "total_chains": len(chains), - "chains_with_peers": chains_with_peers, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - # Try to get peers from the local blockchain node with chain context - response = client.get( - f"{node_url}/rpc/peers?chain_id={target_chain}", - timeout=5 - ) - - if response.status_code == 200: - peers_data = response.json() - output({ - "chain_id": target_chain, - "peers": peers_data.get("peers", peers_data), - "available": True, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - # If no peers endpoint, return meaningful message - output({ - "chain_id": target_chain, - "peers": [], - "message": "No P2P peers available - node running in RPC-only mode", - "available": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get info across all available chains') -@click.pass_context -def info(ctx, chain_id: str, all_chains: bool): - """Get blockchain information across chains""" - try: - config = ctx.obj['config'] - node_url = _get_node_endpoint(ctx) - - if all_chains: - # Get info across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_info = {} - - for chain in chains: - try: - with httpx.Client() as client: - # Get head block for basic info with chain context - response = client.get( - f"{node_url}/rpc/head?chain_id={chain}", - timeout=5 - ) - - if response.status_code == 200: - head_data = response.json() - # Create basic info from head block - all_info[chain] = { - "chain_id": chain, - "height": head_data.get("height"), - "latest_block": head_data.get("hash"), - "timestamp": head_data.get("timestamp"), - "transactions_in_block": head_data.get("tx_count", 0), - "status": "active", - "available": True - } - else: - all_info[chain] = { - "chain_id": chain, - "error": f"HTTP {response.status_code}", - "available": False - } - except Exception as e: - all_info[chain] = { - "chain_id": chain, - "error": str(e), - "available": False - } - - # Count available chains - available_chains = sum(1 for info in all_info.values() if info.get("available", False)) - - output({ - "chains": all_info, - "total_chains": len(chains), - "available_chains": available_chains, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - # Get head block for basic info with chain context - response = client.get( - f"{node_url}/rpc/head?chain_id={target_chain}", - timeout=5 - ) - - if response.status_code == 200: - head_data = response.json() - # Create basic info from head block - info_data = { - "chain_id": target_chain, - "height": head_data.get("height"), - "latest_block": head_data.get("hash"), - "timestamp": head_data.get("timestamp"), - "transactions_in_block": head_data.get("tx_count", 0), - "status": "active", - "available": True, - "query_type": "single_chain" - } - output(info_data, ctx.obj['output_format']) - else: - output({ - "chain_id": target_chain, - "error": f"HTTP {response.status_code}", - "available": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get supply across all available chains') -@click.pass_context -def supply(ctx, chain_id: str, all_chains: bool): - """Get token supply information across chains""" - try: - config = ctx.obj['config'] - node_url = _get_node_endpoint(ctx) - - if all_chains: - # Get supply across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_supply = {} - - for chain in chains: - try: - with httpx.Client() as client: - response = client.get( - f"{node_url}/rpc/supply?chain_id={chain}", - timeout=5 - ) - - if response.status_code == 200: - supply_data = response.json() - all_supply[chain] = { - "chain_id": chain, - "supply": supply_data, - "available": True - } - else: - all_supply[chain] = { - "chain_id": chain, - "error": f"HTTP {response.status_code}", - "available": False - } - except Exception as e: - all_supply[chain] = { - "chain_id": chain, - "error": str(e), - "available": False - } - - # Count chains with available supply data - chains_with_supply = sum(1 for supply in all_supply.values() if supply.get("available", False)) - - output({ - "chains": all_supply, - "total_chains": len(chains), - "chains_with_supply": chains_with_supply, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - response = client.get( - f"{node_url}/rpc/supply?chain_id={target_chain}", - timeout=5 - ) - - if response.status_code == 200: - supply_data = response.json() - output({ - "chain_id": target_chain, - "supply": supply_data, - "available": True, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - output({ - "chain_id": target_chain, - "error": f"HTTP {response.status_code}", - "available": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Get validators across all available chains') -@click.pass_context -def validators(ctx, chain_id: str, all_chains: bool): - """List blockchain validators across chains""" - try: - config = ctx.obj['config'] - node_url = _get_node_endpoint(ctx) - - if all_chains: - # Get validators across all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - all_validators = {} - - for chain in chains: - try: - with httpx.Client() as client: - response = client.get( - f"{node_url}/rpc/validators?chain_id={chain}", - timeout=5 - ) - - if response.status_code == 200: - validators_data = response.json() - all_validators[chain] = { - "chain_id": chain, - "validators": validators_data.get("validators", validators_data), - "available": True - } - else: - all_validators[chain] = { - "chain_id": chain, - "error": f"HTTP {response.status_code}", - "available": False - } - except Exception as e: - all_validators[chain] = { - "chain_id": chain, - "error": str(e), - "available": False - } - - # Count chains with available validators - chains_with_validators = sum(1 for validators in all_validators.values() if validators.get("available", False)) - - output({ - "chains": all_validators, - "total_chains": len(chains), - "chains_with_validators": chains_with_validators, - "query_type": "all_chains" - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - response = client.get( - f"{node_url}/rpc/validators?chain_id={target_chain}", - timeout=5 - ) - - if response.status_code == 200: - validators_data = response.json() - output({ - "chain_id": target_chain, - "validators": validators_data.get("validators", validators_data), - "available": True, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - output({ - "chain_id": target_chain, - "error": f"HTTP {response.status_code}", - "available": False, - "query_type": "single_chain_error" - }, ctx.obj['output_format']) - - except Exception as e: - error(f"Network error: {e}") - -@blockchain.command() -@click.option('--chain-id', required=True, help='Chain ID') -@click.pass_context -def genesis(ctx, chain_id): - """Get the genesis block of a chain""" - config = ctx.obj['config'] - try: - import httpx - with httpx.Client() as client: - # We assume node 1 is running on port 8082, but let's just hit the first configured node - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/blocks/0?chain_id={chain_id}", - timeout=5 - ) - if response.status_code == 200: - output(response.json(), ctx.obj['output_format']) - else: - error(f"Failed to get genesis block: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - -@blockchain.command() -@click.option('--chain-id', required=True, help='Chain ID') -@click.pass_context -def transactions(ctx, chain_id): - """Get latest transactions on a chain""" - config = ctx.obj['config'] - try: - import httpx - with httpx.Client() as client: - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/transactions?chain_id={chain_id}", - timeout=5 - ) - if response.status_code == 200: - output(response.json(), ctx.obj['output_format']) - else: - error(f"Failed to get transactions: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - -@blockchain.command() -@click.option('--chain-id', required=True, help='Chain ID') -@click.pass_context -def head(ctx, chain_id): - """Get the head block of a chain""" - config = ctx.obj['config'] - try: - import httpx - with httpx.Client() as client: - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/head?chain_id={chain_id}", - timeout=5 - ) - if response.status_code == 200: - output(response.json(), ctx.obj['output_format']) - else: - error(f"Failed to get head block: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain-id', required=True, help='Chain ID') -@click.option('--from', 'from_addr', required=True, help='Sender address') -@click.option('--to', required=True, help='Recipient address') -@click.option('--data', required=True, help='Transaction data payload') -@click.option('--nonce', type=int, default=0, help='Nonce') -@click.pass_context -def send(ctx, chain_id, from_addr, to, data, nonce): - """Send a transaction to a chain""" - config = ctx.obj['config'] - try: - import httpx - with httpx.Client() as client: - tx_payload = { - "type": "TRANSFER", - "chain_id": chain_id, - "from_address": from_addr, - "to_address": to, - "value": 0, - "gas_limit": 100000, - "gas_price": 1, - "nonce": nonce, - "data": data, - "signature": "mock_signature" - } - - response = client.post( - f"{_get_node_endpoint(ctx)}/rpc/sendTx", - json=tx_payload, - timeout=5 - ) - if response.status_code in (200, 201): - output(response.json(), ctx.obj['output_format']) - else: - error(f"Failed to send transaction: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--address', required=True, help='Wallet address') -@click.option('--chain-id', help='Specific chain ID to query (default: ait-devnet)') -@click.option('--all-chains', is_flag=True, help='Query balance across all available chains') -@click.pass_context -def balance(ctx, address, chain_id, all_chains): - """Get the balance of an address across chains""" - config = ctx.obj['config'] - try: - import httpx - - if all_chains: - # Query all available chains - chains = ['ait-devnet', 'ait-testnet'] # TODO: Get from chain registry - balances = {} - - with httpx.Client() as client: - for chain in chains: - try: - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/getBalance/{address}?chain_id={chain}", - timeout=5 - ) - if response.status_code == 200: - balances[chain] = response.json() - else: - balances[chain] = {"error": f"HTTP {response.status_code}"} - except Exception as e: - balances[chain] = {"error": str(e)} - - output({ - "address": address, - "chains": balances, - "total_chains": len(chains), - "successful_queries": sum(1 for b in balances.values() if "error" not in b) - }, ctx.obj['output_format']) - - else: - # Query specific chain (default to ait-devnet if not specified) - target_chain = chain_id or 'ait-devnet' - - with httpx.Client() as client: - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/getBalance/{address}?chain_id={target_chain}", - timeout=5 - ) - if response.status_code == 200: - balance_data = response.json() - output({ - "address": address, - "chain_id": target_chain, - "balance": balance_data, - "query_type": "single_chain" - }, ctx.obj['output_format']) - else: - error(f"Failed to get balance: {response.status_code} - {response.text}") - - except Exception as e: - error(f"Network error: {e}") - -@blockchain.command() -@click.option('--address', required=True, help='Wallet address') -@click.option('--amount', type=int, default=1000, help='Amount to mint') -@click.pass_context -def faucet(ctx, address, amount): - """Mint devnet funds to an address""" - config = ctx.obj['config'] - try: - import httpx - with httpx.Client() as client: - response = client.post( - f"{_get_node_endpoint(ctx)}/rpc/admin/mintFaucet", - json={"address": address, "amount": amount, "chain_id": "ait-devnet"}, - timeout=5 - ) - if response.status_code in (200, 201): - output(response.json(), ctx.obj['output_format']) - else: - error(f"Failed to use faucet: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - - -@blockchain.command() -@click.option('--chain', required=True, help='Chain ID to verify (e.g., ait-mainnet, ait-devnet)') -@click.option('--genesis-hash', help='Expected genesis hash to verify against') -@click.option('--verify-signatures', is_flag=True, default=True, help='Verify genesis block signatures') -@click.pass_context -def verify_genesis(ctx, chain: str, genesis_hash: Optional[str], verify_signatures: bool): - """Verify genesis block integrity for a specific chain""" - try: - import httpx - from ..utils import success - - with httpx.Client() as client: - # Get genesis block for the specified chain - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/getGenesisBlock?chain_id={chain}", - timeout=10 - ) - - if response.status_code != 200: - error(f"Failed to get genesis block for chain '{chain}': {response.status_code}") - return - - genesis_data = response.json() - - # Verification results - verification_results = { - "chain_id": chain, - "genesis_block": genesis_data, - "verification_passed": True, - "checks": {} - } - - # Check 1: Genesis hash verification - if genesis_hash: - actual_hash = genesis_data.get("hash") - if actual_hash == genesis_hash: - verification_results["checks"]["hash_match"] = { - "status": "passed", - "expected": genesis_hash, - "actual": actual_hash - } - success(f"✅ Genesis hash matches expected value") - else: - verification_results["checks"]["hash_match"] = { - "status": "failed", - "expected": genesis_hash, - "actual": actual_hash - } - verification_results["verification_passed"] = False - error(f"❌ Genesis hash mismatch!") - error(f"Expected: {genesis_hash}") - error(f"Actual: {actual_hash}") - - # Check 2: Genesis block structure - required_fields = ["hash", "previous_hash", "timestamp", "transactions", "nonce"] - missing_fields = [field for field in required_fields if field not in genesis_data] - - if not missing_fields: - verification_results["checks"]["structure"] = { - "status": "passed", - "required_fields": required_fields - } - success(f"✅ Genesis block structure is valid") - else: - verification_results["checks"]["structure"] = { - "status": "failed", - "missing_fields": missing_fields - } - verification_results["verification_passed"] = False - error(f"❌ Genesis block missing required fields: {missing_fields}") - - # Check 3: Signature verification (if requested) - if verify_signatures and "signature" in genesis_data: - # This would implement actual signature verification - # For now, we'll just check if signature exists - verification_results["checks"]["signature"] = { - "status": "passed", - "signature_present": True - } - success(f"✅ Genesis block signature is present") - elif verify_signatures: - verification_results["checks"]["signature"] = { - "status": "warning", - "message": "No signature found in genesis block" - } - warning(f"⚠️ No signature found in genesis block") - - # Check 4: Previous hash should be null/empty for genesis - prev_hash = genesis_data.get("previous_hash") - if prev_hash in [None, "", "0", "0x0000000000000000000000000000000000000000000000000000000000000000"]: - verification_results["checks"]["previous_hash"] = { - "status": "passed", - "previous_hash": prev_hash - } - success(f"✅ Genesis block previous hash is correct (null)") - else: - verification_results["checks"]["previous_hash"] = { - "status": "failed", - "previous_hash": prev_hash - } - verification_results["verification_passed"] = False - error(f"❌ Genesis block previous hash should be null") - - # Final result - if verification_results["verification_passed"]: - success(f"🎉 Genesis block verification PASSED for chain '{chain}'") - else: - error(f"❌ Genesis block verification FAILED for chain '{chain}'") - - output(verification_results, ctx.obj['output_format']) - - except Exception as e: - error(f"Failed to verify genesis block: {e}") - - -@blockchain.command() -@click.option('--chain', required=True, help='Chain ID to get genesis hash for') -@click.pass_context -def genesis_hash(ctx, chain: str): - """Get the genesis block hash for a specific chain""" - try: - import httpx - from ..utils import success - - with httpx.Client() as client: - response = client.get( - f"{_get_node_endpoint(ctx)}/rpc/getGenesisBlock?chain_id={chain}", - timeout=10 - ) - - if response.status_code != 200: - error(f"Failed to get genesis block for chain '{chain}': {response.status_code}") - return - - genesis_data = response.json() - genesis_hash_value = genesis_data.get("hash") - - if genesis_hash_value: - success(f"Genesis hash for chain '{chain}':") - output({ - "chain_id": chain, - "genesis_hash": genesis_hash_value, - "genesis_block": { - "hash": genesis_hash_value, - "timestamp": genesis_data.get("timestamp"), - "transaction_count": len(genesis_data.get("transactions", [])), - "nonce": genesis_data.get("nonce") - } - }, ctx.obj['output_format']) - else: - error(f"No hash found in genesis block for chain '{chain}'") - - except Exception as e: - error(f"Failed to get genesis hash: {e}") - - -def warning(message: str): - """Display warning message""" - click.echo(click.style(f"⚠️ {message}", fg='yellow')) diff --git a/cli/aitbc_cli/commands/deployment.py b/cli/aitbc_cli/commands/deployment.py deleted file mode 100755 index 54afde49..00000000 --- a/cli/aitbc_cli/commands/deployment.py +++ /dev/null @@ -1,378 +0,0 @@ -"""Production deployment and scaling commands for AITBC CLI""" - -import click -import asyncio -import json -from datetime import datetime -from typing import Optional -from ..core.deployment import ( - ProductionDeployment, ScalingPolicy, DeploymentStatus -) -from ..utils import output, error, success - -@click.group() -def deploy(): - """Production deployment and scaling commands""" - pass - -@deploy.command() -@click.argument('name') -@click.argument('environment') -@click.argument('region') -@click.argument('instance_type') -@click.argument('min_instances', type=int) -@click.argument('max_instances', type=int) -@click.argument('desired_instances', type=int) -@click.argument('port', type=int) -@click.argument('domain') -@click.option('--db-host', default='localhost', help='Database host') -@click.option('--db-port', default=5432, help='Database port') -@click.option('--db-name', default='aitbc', help='Database name') -@click.pass_context -def create(ctx, name, environment, region, instance_type, min_instances, max_instances, desired_instances, port, domain, db_host, db_port, db_name): - """Create a new deployment configuration""" - try: - deployment = ProductionDeployment() - - # Database configuration - database_config = { - "host": db_host, - "port": db_port, - "name": db_name, - "ssl_enabled": True if environment == "production" else False - } - - # Create deployment - deployment_id = asyncio.run(deployment.create_deployment( - name=name, - environment=environment, - region=region, - instance_type=instance_type, - min_instances=min_instances, - max_instances=max_instances, - desired_instances=desired_instances, - port=port, - domain=domain, - database_config=database_config - )) - - if deployment_id: - success(f"Deployment configuration created! ID: {deployment_id}") - - deployment_data = { - "Deployment ID": deployment_id, - "Name": name, - "Environment": environment, - "Region": region, - "Instance Type": instance_type, - "Min Instances": min_instances, - "Max Instances": max_instances, - "Desired Instances": desired_instances, - "Port": port, - "Domain": domain, - "Status": "pending", - "Created": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - - output(deployment_data, ctx.obj.get('output_format', 'table')) - else: - error("Failed to create deployment configuration") - raise click.Abort() - - except Exception as e: - error(f"Error creating deployment: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.argument('deployment_id') -@click.pass_context -def start(ctx, deployment_id): - """Deploy the application to production""" - try: - deployment = ProductionDeployment() - - # Deploy application - success_deploy = asyncio.run(deployment.deploy_application(deployment_id)) - - if success_deploy: - success(f"Deployment {deployment_id} started successfully!") - - deployment_data = { - "Deployment ID": deployment_id, - "Status": "running", - "Started": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - - output(deployment_data, ctx.obj.get('output_format', 'table')) - else: - error(f"Failed to start deployment {deployment_id}") - raise click.Abort() - - except Exception as e: - error(f"Error starting deployment: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.argument('deployment_id') -@click.argument('target_instances', type=int) -@click.option('--reason', default='manual', help='Scaling reason') -@click.pass_context -def scale(ctx, deployment_id, target_instances, reason): - """Scale a deployment to target instance count""" - try: - deployment = ProductionDeployment() - - # Scale deployment - success_scale = asyncio.run(deployment.scale_deployment(deployment_id, target_instances, reason)) - - if success_scale: - success(f"Deployment {deployment_id} scaled to {target_instances} instances!") - - scaling_data = { - "Deployment ID": deployment_id, - "Target Instances": target_instances, - "Reason": reason, - "Status": "completed", - "Scaled": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - - output(scaling_data, ctx.obj.get('output_format', 'table')) - else: - error(f"Failed to scale deployment {deployment_id}") - raise click.Abort() - - except Exception as e: - error(f"Error scaling deployment: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.argument('deployment_id') -@click.pass_context -def status(ctx, deployment_id): - """Get comprehensive deployment status""" - try: - deployment = ProductionDeployment() - - # Get deployment status - status_data = asyncio.run(deployment.get_deployment_status(deployment_id)) - - if not status_data: - error(f"Deployment {deployment_id} not found") - raise click.Abort() - - # Format deployment info - deployment_info = status_data["deployment"] - info_data = [ - {"Metric": "Deployment ID", "Value": deployment_info["deployment_id"]}, - {"Metric": "Name", "Value": deployment_info["name"]}, - {"Metric": "Environment", "Value": deployment_info["environment"]}, - {"Metric": "Region", "Value": deployment_info["region"]}, - {"Metric": "Instance Type", "Value": deployment_info["instance_type"]}, - {"Metric": "Min Instances", "Value": deployment_info["min_instances"]}, - {"Metric": "Max Instances", "Value": deployment_info["max_instances"]}, - {"Metric": "Desired Instances", "Value": deployment_info["desired_instances"]}, - {"Metric": "Port", "Value": deployment_info["port"]}, - {"Metric": "Domain", "Value": deployment_info["domain"]}, - {"Metric": "Health Status", "Value": "Healthy" if status_data["health_status"] else "Unhealthy"}, - {"Metric": "Uptime", "Value": f"{status_data['uptime_percentage']:.2f}%"} - ] - - output(info_data, ctx.obj.get('output_format', 'table'), title=f"Deployment Status: {deployment_id}") - - # Show metrics if available - if status_data["metrics"]: - metrics = status_data["metrics"] - metrics_data = [ - {"Metric": "CPU Usage", "Value": f"{metrics['cpu_usage']:.1f}%"}, - {"Metric": "Memory Usage", "Value": f"{metrics['memory_usage']:.1f}%"}, - {"Metric": "Disk Usage", "Value": f"{metrics['disk_usage']:.1f}%"}, - {"Metric": "Request Count", "Value": metrics['request_count']}, - {"Metric": "Error Rate", "Value": f"{metrics['error_rate']:.2f}%"}, - {"Metric": "Response Time", "Value": f"{metrics['response_time']:.1f}ms"}, - {"Metric": "Active Instances", "Value": metrics['active_instances']} - ] - - output(metrics_data, ctx.obj.get('output_format', 'table'), title="Performance Metrics") - - # Show recent scaling events - if status_data["recent_scaling_events"]: - events = status_data["recent_scaling_events"] - events_data = [ - { - "Event ID": event["event_id"][:8], - "Type": event["scaling_type"], - "From": event["old_instances"], - "To": event["new_instances"], - "Reason": event["trigger_reason"], - "Success": "Yes" if event["success"] else "No", - "Time": event["triggered_at"] - } - for event in events - ] - - output(events_data, ctx.obj.get('output_format', 'table'), title="Recent Scaling Events") - - except Exception as e: - error(f"Error getting deployment status: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format') -@click.pass_context -def overview(ctx, format): - """Get overview of all deployments""" - try: - deployment = ProductionDeployment() - - # Get cluster overview - overview_data = asyncio.run(deployment.get_cluster_overview()) - - if not overview_data: - error("No deployment data available") - raise click.Abort() - - # Cluster metrics - cluster_data = [ - {"Metric": "Total Deployments", "Value": overview_data["total_deployments"]}, - {"Metric": "Running Deployments", "Value": overview_data["running_deployments"]}, - {"Metric": "Total Instances", "Value": overview_data["total_instances"]}, - {"Metric": "Health Check Coverage", "Value": f"{overview_data['health_check_coverage']:.1%}"}, - {"Metric": "Recent Scaling Events", "Value": overview_data["recent_scaling_events"]}, - {"Metric": "Scaling Success Rate", "Value": f"{overview_data['successful_scaling_rate']:.1%}"} - ] - - output(cluster_data, ctx.obj.get('output_format', format), title="Cluster Overview") - - # Aggregate metrics - if "aggregate_metrics" in overview_data: - metrics = overview_data["aggregate_metrics"] - metrics_data = [ - {"Metric": "Average CPU Usage", "Value": f"{metrics['total_cpu_usage']:.1f}%"}, - {"Metric": "Average Memory Usage", "Value": f"{metrics['total_memory_usage']:.1f}%"}, - {"Metric": "Average Disk Usage", "Value": f"{metrics['total_disk_usage']:.1f}%"}, - {"Metric": "Average Response Time", "Value": f"{metrics['average_response_time']:.1f}ms"}, - {"Metric": "Average Error Rate", "Value": f"{metrics['average_error_rate']:.2f}%"}, - {"Metric": "Average Uptime", "Value": f"{metrics['average_uptime']:.1f}%"} - ] - - output(metrics_data, ctx.obj.get('output_format', format), title="Aggregate Performance Metrics") - - except Exception as e: - error(f"Error getting cluster overview: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.argument('deployment_id') -@click.option('--interval', default=60, help='Update interval in seconds') -@click.pass_context -def monitor(ctx, deployment_id, interval): - """Monitor deployment performance in real-time""" - try: - deployment = ProductionDeployment() - - # Real-time monitoring - from rich.console import Console - from rich.live import Live - from rich.table import Table - import time - - console = Console() - - def generate_monitor_table(): - try: - status_data = asyncio.run(deployment.get_deployment_status(deployment_id)) - - if not status_data: - return f"Deployment {deployment_id} not found" - - deployment_info = status_data["deployment"] - metrics = status_data.get("metrics") - - table = Table(title=f"Deployment Monitor - {deployment_info['name']} ({deployment_id[:8]}) - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - table.add_column("Metric", style="cyan") - table.add_column("Value", style="green") - - table.add_row("Environment", deployment_info["environment"]) - table.add_row("Desired Instances", str(deployment_info["desired_instances"])) - table.add_row("Health Status", "✅ Healthy" if status_data["health_status"] else "❌ Unhealthy") - table.add_row("Uptime", f"{status_data['uptime_percentage']:.2f}%") - - if metrics: - table.add_row("CPU Usage", f"{metrics['cpu_usage']:.1f}%") - table.add_row("Memory Usage", f"{metrics['memory_usage']:.1f}%") - table.add_row("Disk Usage", f"{metrics['disk_usage']:.1f}%") - table.add_row("Request Count", str(metrics['request_count'])) - table.add_row("Error Rate", f"{metrics['error_rate']:.2f}%") - table.add_row("Response Time", f"{metrics['response_time']:.1f}ms") - table.add_row("Active Instances", str(metrics['active_instances'])) - - return table - except Exception as e: - return f"Error getting deployment data: {e}" - - with Live(generate_monitor_table(), refresh_per_second=1) as live: - try: - while True: - live.update(generate_monitor_table()) - time.sleep(interval) - except KeyboardInterrupt: - console.print("\n[yellow]Monitoring stopped by user[/yellow]") - - except Exception as e: - error(f"Error during monitoring: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.argument('deployment_id') -@click.pass_context -def auto_scale(ctx, deployment_id): - """Trigger auto-scaling evaluation for a deployment""" - try: - deployment = ProductionDeployment() - - # Trigger auto-scaling - success_auto = asyncio.run(deployment.auto_scale_deployment(deployment_id)) - - if success_auto: - success(f"Auto-scaling evaluation completed for deployment {deployment_id}") - else: - error(f"Auto-scaling evaluation failed for deployment {deployment_id}") - raise click.Abort() - - except Exception as e: - error(f"Error in auto-scaling: {str(e)}") - raise click.Abort() - -@deploy.command() -@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format') -@click.pass_context -def list_deployments(ctx, format): - """List all deployments""" - try: - deployment = ProductionDeployment() - - # Get all deployment statuses - deployments = [] - for deployment_id in deployment.deployments.keys(): - status_data = asyncio.run(deployment.get_deployment_status(deployment_id)) - if status_data: - deployment_info = status_data["deployment"] - deployments.append({ - "Deployment ID": deployment_info["deployment_id"][:8], - "Name": deployment_info["name"], - "Environment": deployment_info["environment"], - "Instances": f"{deployment_info['desired_instances']}/{deployment_info['max_instances']}", - "Status": "Running" if status_data["health_status"] else "Stopped", - "Uptime": f"{status_data['uptime_percentage']:.1f}%", - "Created": deployment_info["created_at"] - }) - - if not deployments: - output("No deployments found", ctx.obj.get('output_format', 'table')) - return - - output(deployments, ctx.obj.get('output_format', format), title="All Deployments") - - except Exception as e: - error(f"Error listing deployments: {str(e)}") - raise click.Abort() diff --git a/cli/aitbc_cli/commands/miner.py.backup b/cli/aitbc_cli/commands/miner.py.backup deleted file mode 100755 index 21037f6b..00000000 --- a/cli/aitbc_cli/commands/miner.py.backup +++ /dev/null @@ -1,637 +0,0 @@ -"""Miner commands for AITBC CLI""" - -import click -import httpx -import json -import time -import concurrent.futures -from typing import Optional, Dict, Any, List -from ..utils import output, error, success - - -@click.group(invoke_without_command=True) -@click.pass_context -def miner(ctx): - """Register as miner and process jobs""" - # Set role for miner commands - this will be used by parent context - ctx.ensure_object(dict) - # Set role at the highest level context (CLI root) - ctx.find_root().detected_role = 'miner' - - # If no subcommand was invoked, show help - if ctx.invoked_subcommand is None: - click.echo(ctx.get_help()) - - -@miner.command() -@click.option("--gpu", help="GPU model name") -@click.option("--memory", type=int, help="GPU memory in GB") -@click.option("--cuda-cores", type=int, help="Number of CUDA cores") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def register(ctx, gpu: Optional[str], memory: Optional[int], - cuda_cores: Optional[int], miner_id: str): - """Register as a miner with the coordinator""" - config = ctx.obj['config'] - - # Build capabilities - capabilities = {} - if gpu: - capabilities["gpu"] = {"model": gpu} - if memory: - if "gpu" not in capabilities: - capabilities["gpu"] = {} - capabilities["gpu"]["memory_gb"] = memory - if cuda_cores: - if "gpu" not in capabilities: - capabilities["gpu"] = {} - capabilities["gpu"]["cuda_cores"] = cuda_cores - - # Default capabilities if none provided - if not capabilities: - capabilities = { - "cpu": {"cores": 4}, - "memory": {"gb": 16} - } - - try: - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/register", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={"capabilities": capabilities} - ) - - if response.status_code in (200, 204): - output({ - "miner_id": miner_id, - "status": "registered", - "capabilities": capabilities - }, ctx.obj['output_format']) - else: - error(f"Failed to register: {response.status_code} - {response.text}") - except Exception as e: - error(f"Network error: {e}") - - -@miner.command() -@click.option("--wait", type=int, default=5, help="Max wait time in seconds") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def poll(ctx, wait: int, miner_id: str): - """Poll for a single job""" - config = ctx.obj['config'] - - try: - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/poll", - json={"max_wait_seconds": 5}, - headers={ - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - timeout=wait + 5 - ) - - if response.status_code in (200, 204): - if response.status_code == 204: - output({"message": "No jobs available"}, ctx.obj['output_format']) - else: - job = response.json() - if job: - output(job, ctx.obj['output_format']) - else: - output({"message": "No jobs available"}, ctx.obj['output_format']) - else: - error(f"Failed to poll: {response.status_code}") - except httpx.TimeoutException: - output({"message": f"No jobs available within {wait} seconds"}, ctx.obj['output_format']) - except Exception as e: - error(f"Network error: {e}") - - -@miner.command() -@click.option("--jobs", type=int, default=1, help="Number of jobs to process") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def mine(ctx, jobs: int, miner_id: str): - """Mine continuously for specified number of jobs""" - config = ctx.obj['config'] - - processed = 0 - while processed < jobs: - try: - with httpx.Client() as client: - # Poll for job - response = client.post( - f"{config.coordinator_url}/v1/miners/poll", - json={"max_wait_seconds": 5}, - headers={ - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - timeout=30 - ) - - if response.status_code in (200, 204): - if response.status_code == 204: - time.sleep(5) - continue - job = response.json() - if job: - job_id = job.get('job_id') - output({ - "job_id": job_id, - "status": "processing", - "job_number": processed + 1 - }, ctx.obj['output_format']) - - # Simulate processing (in real implementation, do actual work) - time.sleep(2) - - # Submit result - result_response = client.post( - f"{config.coordinator_url}/v1/miners/{job_id}/result", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={ - "result": {"output": f"Processed job {job_id}"}, - "metrics": {} - } - ) - - if result_response.status_code == 200: - success(f"Job {job_id} completed successfully") - processed += 1 - else: - error(f"Failed to submit result: {result_response.status_code}") - else: - # No job available, wait a bit - time.sleep(5) - else: - error(f"Failed to poll: {response.status_code}") - break - - except Exception as e: - error(f"Error: {e}") - break - - output({ - "total_processed": processed, - "miner_id": miner_id - }, ctx.obj['output_format']) - - -@miner.command() -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def heartbeat(ctx, miner_id: str): - """Send heartbeat to coordinator""" - config = ctx.obj['config'] - - try: - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/heartbeat", - headers={ - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={ - "inflight": 0, - "status": "ONLINE", - "metadata": {} - } - ) - - if response.status_code in (200, 204): - output({ - "miner_id": miner_id, - "status": "heartbeat_sent", - "timestamp": time.time() - }, ctx.obj['output_format']) - else: - error(f"Failed to send heartbeat: {response.status_code}") - except Exception as e: - error(f"Network error: {e}") - - -@miner.command() -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def status(ctx, miner_id: str): - """Check miner status""" - config = ctx.obj['config'] - - # This would typically query a miner status endpoint - # For now, we'll just show the miner info - output({ - "miner_id": miner_id, - "coordinator": config.coordinator_url, - "status": "active" - }, ctx.obj['output_format']) - - -@miner.command() -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.option("--from-time", help="Filter from timestamp (ISO format)") -@click.option("--to-time", help="Filter to timestamp (ISO format)") -@click.pass_context -def earnings(ctx, miner_id: str, from_time: Optional[str], to_time: Optional[str]): - """Show miner earnings""" - config = ctx.obj['config'] - - try: - params = {"miner_id": miner_id} - if from_time: - params["from_time"] = from_time - if to_time: - params["to_time"] = to_time - - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/{miner_id}/earnings", - params=params, - headers={"X-Api-Key": config.api_key or ""} - ) - - if response.status_code in (200, 204): - data = response.json() - output(data, ctx.obj['output_format']) - else: - error(f"Failed to get earnings: {response.status_code}") - ctx.exit(1) - except Exception as e: - error(f"Network error: {e}") - ctx.exit(1) - - -@miner.command(name="update-capabilities") -@click.option("--gpu", help="GPU model name") -@click.option("--memory", type=int, help="GPU memory in GB") -@click.option("--cuda-cores", type=int, help="Number of CUDA cores") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def update_capabilities(ctx, gpu: Optional[str], memory: Optional[int], - cuda_cores: Optional[int], miner_id: str): - """Update miner GPU capabilities""" - config = ctx.obj['config'] - - capabilities = {} - if gpu: - capabilities["gpu"] = {"model": gpu} - if memory: - if "gpu" not in capabilities: - capabilities["gpu"] = {} - capabilities["gpu"]["memory_gb"] = memory - if cuda_cores: - if "gpu" not in capabilities: - capabilities["gpu"] = {} - capabilities["gpu"]["cuda_cores"] = cuda_cores - - if not capabilities: - error("No capabilities specified. Use --gpu, --memory, or --cuda-cores.") - return - - try: - with httpx.Client() as client: - response = client.put( - f"{config.coordinator_url}/v1/miners/{miner_id}/capabilities", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "" - }, - json={"capabilities": capabilities} - ) - - if response.status_code in (200, 204): - output({ - "miner_id": miner_id, - "status": "capabilities_updated", - "capabilities": capabilities - }, ctx.obj['output_format']) - else: - error(f"Failed to update capabilities: {response.status_code}") - ctx.exit(1) - except Exception as e: - error(f"Network error: {e}") - ctx.exit(1) - - -@miner.command() -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.option("--force", is_flag=True, help="Force deregistration without confirmation") -@click.pass_context -def deregister(ctx, miner_id: str, force: bool): - """Deregister miner from the coordinator""" - if not force: - if not click.confirm(f"Deregister miner '{miner_id}'?"): - click.echo("Cancelled.") - return - - config = ctx.obj['config'] - - try: - with httpx.Client() as client: - response = client.delete( - f"{config.coordinator_url}/v1/miners/{miner_id}", - headers={"X-Api-Key": config.api_key or ""} - ) - - if response.status_code in (200, 204): - output({ - "miner_id": miner_id, - "status": "deregistered" - }, ctx.obj['output_format']) - else: - error(f"Failed to deregister: {response.status_code}") - ctx.exit(1) - except Exception as e: - error(f"Network error: {e}") - ctx.exit(1) - - -@miner.command() -@click.option("--limit", default=10, help="Number of jobs to show") -@click.option("--type", "job_type", help="Filter by job type") -@click.option("--min-reward", type=float, help="Minimum reward threshold") -@click.option("--status", "job_status", help="Filter by status (pending, running, completed, failed)") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def jobs(ctx, limit: int, job_type: Optional[str], min_reward: Optional[float], - job_status: Optional[str], miner_id: str): - """List miner jobs with filtering""" - config = ctx.obj['config'] - - try: - params = {"limit": limit, "miner_id": miner_id} - if job_type: - params["type"] = job_type - if min_reward is not None: - params["min_reward"] = min_reward - if job_status: - params["status"] = job_status - - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/{miner_id}/jobs", - params=params, - headers={"X-Api-Key": config.api_key or ""} - ) - - if response.status_code in (200, 204): - data = response.json() - output(data, ctx.obj['output_format']) - else: - error(f"Failed to get jobs: {response.status_code}") - ctx.exit(1) - except Exception as e: - error(f"Network error: {e}") - ctx.exit(1) - - -def _process_single_job(config, miner_id: str, worker_id: int) -> Dict[str, Any]: - """Process a single job (used by concurrent mine)""" - try: - with httpx.Client() as http_client: - response = http_client.post( - f"{config.coordinator_url}/v1/miners/poll", - json={"max_wait_seconds": 5}, - headers={ - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - timeout=30 - ) - - if response.status_code == 204: - return {"worker": worker_id, "status": "no_job"} - if response.status_code == 200: - job = response.json() - if job: - job_id = job.get('job_id') - time.sleep(2) # Simulate processing - - result_response = http_client.post( - f"{config.coordinator_url}/v1/miners/{job_id}/result", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={"result": {"output": f"Processed by worker {worker_id}"}, "metrics": {}} - ) - - return { - "worker": worker_id, - "job_id": job_id, - "status": "completed" if result_response.status_code == 200 else "failed" - } - return {"worker": worker_id, "status": "no_job"} - except Exception as e: - return {"worker": worker_id, "status": "error", "error": str(e)} - - -def _run_ollama_inference(ollama_url: str, model: str, prompt: str) -> Dict[str, Any]: - """Run inference through local Ollama instance""" - try: - with httpx.Client(timeout=120) as client: - response = client.post( - f"{ollama_url}/api/generate", - json={ - "model": model, - "prompt": prompt, - "stream": False - } - ) - if response.status_code == 200: - data = response.json() - return { - "response": data.get("response", ""), - "model": data.get("model", model), - "total_duration": data.get("total_duration", 0), - "eval_count": data.get("eval_count", 0), - "eval_duration": data.get("eval_duration", 0), - } - else: - return {"error": f"Ollama returned {response.status_code}"} - except Exception as e: - return {"error": str(e)} - - -@miner.command(name="mine-ollama") -@click.option("--jobs", type=int, default=1, help="Number of jobs to process") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.option("--ollama-url", default="http://localhost:11434", help="Ollama API URL") -@click.option("--model", default="gemma3:1b", help="Ollama model to use") -@click.pass_context -def mine_ollama(ctx, jobs: int, miner_id: str, ollama_url: str, model: str): - """Mine jobs using local Ollama for GPU inference""" - config = ctx.obj['config'] - - # Verify Ollama is reachable - try: - with httpx.Client(timeout=5) as client: - resp = client.get(f"{ollama_url}/api/tags") - if resp.status_code != 200: - error(f"Cannot reach Ollama at {ollama_url}") - return - models = [m["name"] for m in resp.json().get("models", [])] - if model not in models: - error(f"Model '{model}' not found. Available: {', '.join(models)}") - return - success(f"Ollama connected: {ollama_url} | model: {model}") - except Exception as e: - error(f"Cannot connect to Ollama: {e}") - return - - processed = 0 - while processed < jobs: - try: - with httpx.Client() as client: - response = client.post( - f"{config.coordinator_url}/v1/miners/poll", - json={"max_wait_seconds": 10}, - headers={ - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - timeout=30 - ) - - if response.status_code == 204: - time.sleep(5) - continue - - if response.status_code != 200: - error(f"Failed to poll: {response.status_code}") - break - - job = response.json() - if not job: - time.sleep(5) - continue - - job_id = job.get('job_id') - payload = job.get('payload', {}) - prompt = payload.get('prompt', '') - job_model = payload.get('model', model) - - output({ - "job_id": job_id, - "status": "processing", - "prompt": prompt[:80] + ("..." if len(prompt) > 80 else ""), - "model": job_model, - "job_number": processed + 1 - }, ctx.obj['output_format']) - - # Run inference through Ollama - start_time = time.time() - ollama_result = _run_ollama_inference(ollama_url, job_model, prompt) - duration_ms = int((time.time() - start_time) * 1000) - - if "error" in ollama_result: - error(f"Ollama inference failed: {ollama_result['error']}") - # Submit failure - client.post( - f"{config.coordinator_url}/v1/miners/{job_id}/fail", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={"error_code": "INFERENCE_FAILED", "error_message": ollama_result['error'], "metrics": {}} - ) - continue - - # Submit successful result - result_response = client.post( - f"{config.coordinator_url}/v1/miners/{job_id}/result", - headers={ - "Content-Type": "application/json", - "X-Api-Key": config.api_key or "", - "X-Miner-ID": miner_id - }, - json={ - "result": { - "response": ollama_result.get("response", ""), - "model": ollama_result.get("model", job_model), - "provider": "ollama", - "eval_count": ollama_result.get("eval_count", 0), - }, - "metrics": { - "duration_ms": duration_ms, - "eval_count": ollama_result.get("eval_count", 0), - "eval_duration": ollama_result.get("eval_duration", 0), - "total_duration": ollama_result.get("total_duration", 0), - } - } - ) - - if result_response.status_code == 200: - success(f"Job {job_id} completed via Ollama ({duration_ms}ms)") - processed += 1 - else: - error(f"Failed to submit result: {result_response.status_code}") - - except Exception as e: - error(f"Error: {e}") - break - - output({ - "total_processed": processed, - "miner_id": miner_id, - "model": model, - "provider": "ollama" - }, ctx.obj['output_format']) - - -@miner.command(name="concurrent-mine") -@click.option("--workers", type=int, default=2, help="Number of concurrent workers") -@click.option("--jobs", "total_jobs", type=int, default=5, help="Total jobs to process") -@click.option("--miner-id", default="cli-miner", help="Miner ID") -@click.pass_context -def concurrent_mine(ctx, workers: int, total_jobs: int, miner_id: str): - """Mine with concurrent job processing""" - config = ctx.obj['config'] - - success(f"Starting concurrent mining: {workers} workers, {total_jobs} jobs") - - completed = 0 - failed = 0 - - with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: - remaining = total_jobs - while remaining > 0: - batch_size = min(remaining, workers) - futures = [ - executor.submit(_process_single_job, config, miner_id, i) - for i in range(batch_size) - ] - - for future in concurrent.futures.as_completed(futures): - result = future.result() - if result.get("status") == "completed": - completed += 1 - remaining -= 1 - output(result, ctx.obj['output_format']) - elif result.get("status") == "no_job": - time.sleep(2) - else: - failed += 1 - remaining -= 1 - - output({ - "status": "finished", - "completed": completed, - "failed": failed, - "workers": workers - }, ctx.obj['output_format']) diff --git a/cli/aitbc_cli/commands/wallet.py.backup b/cli/aitbc_cli/commands/wallet.py.backup deleted file mode 100755 index a356cfe3..00000000 --- a/cli/aitbc_cli/commands/wallet.py.backup +++ /dev/null @@ -1,2229 +0,0 @@ -"""Wallet commands for AITBC CLI""" - -import click -import httpx -import json -import os -import shutil -import yaml -from pathlib import Path -from typing import Optional, Dict, Any, List -from datetime import datetime, timedelta -from ..utils import output, error, success, encrypt_value, decrypt_value -import getpass - - -def _get_wallet_password(wallet_name: str) -> str: - """Get or prompt for wallet encryption password""" - # Try to get from keyring first - try: - import keyring - - password = keyring.get_password("aitbc-wallet", wallet_name) - if password: - return password - except Exception: - pass - - # Prompt for password - while True: - password = getpass.getpass(f"Enter password for wallet '{wallet_name}': ") - if not password: - error("Password cannot be empty") - continue - - confirm = getpass.getpass("Confirm password: ") - if password != confirm: - error("Passwords do not match") - continue - - # Store in keyring for future use - try: - import keyring - - keyring.set_password("aitbc-wallet", wallet_name, password) - except Exception: - pass - - return password - - -def _save_wallet(wallet_path: Path, wallet_data: Dict[str, Any], password: str = None): - """Save wallet with encrypted private key""" - # Encrypt private key if provided - if password and "private_key" in wallet_data: - wallet_data["private_key"] = encrypt_value(wallet_data["private_key"], password) - wallet_data["encrypted"] = True - - # Save wallet - with open(wallet_path, "w") as f: - json.dump(wallet_data, f, indent=2) - - -def _load_wallet(wallet_path: Path, wallet_name: str) -> Dict[str, Any]: - """Load wallet and decrypt private key if needed""" - with open(wallet_path, "r") as f: - wallet_data = json.load(f) - - # Decrypt private key if encrypted - if wallet_data.get("encrypted") and "private_key" in wallet_data: - password = _get_wallet_password(wallet_name) - try: - wallet_data["private_key"] = decrypt_value( - wallet_data["private_key"], password - ) - except Exception: - error("Invalid password for wallet") - raise click.Abort() - - return wallet_data - - -@click.group() -@click.option("--wallet-name", help="Name of the wallet to use") -@click.option( - "--wallet-path", help="Direct path to wallet file (overrides --wallet-name)" -) -@click.option( - "--use-daemon", is_flag=True, help="Use wallet daemon for operations" -) -@click.pass_context -def wallet(ctx, wallet_name: Optional[str], wallet_path: Optional[str], use_daemon: bool): - """Manage your AITBC wallets and transactions""" - # Ensure wallet object exists - ctx.ensure_object(dict) - - # Store daemon mode preference - ctx.obj["use_daemon"] = use_daemon - - # Initialize dual-mode adapter - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=use_daemon) - ctx.obj["wallet_adapter"] = adapter - - # If direct wallet path is provided, use it - if wallet_path: - wp = Path(wallet_path) - wp.parent.mkdir(parents=True, exist_ok=True) - ctx.obj["wallet_name"] = wp.stem - ctx.obj["wallet_dir"] = wp.parent - ctx.obj["wallet_path"] = wp - return - - # Set wallet directory - wallet_dir = Path.home() / ".aitbc" / "wallets" - wallet_dir.mkdir(parents=True, exist_ok=True) - - # Set active wallet - if not wallet_name: - # Try to get from config or use 'default' - config_file = Path.home() / ".aitbc" / "config.yaml" - config = None - if config_file.exists(): - with open(config_file, "r") as f: - config = yaml.safe_load(f) - if config: - wallet_name = config.get("active_wallet", "default") - else: - wallet_name = "default" - else: - wallet_name = "default" - else: - # Load config for other operations - config_file = Path.home() / ".aitbc" / "config.yaml" - config = None - if config_file.exists(): - with open(config_file, "r") as f: - config = yaml.safe_load(f) - - ctx.obj["wallet_name"] = wallet_name - ctx.obj["wallet_dir"] = wallet_dir - ctx.obj["wallet_path"] = wallet_dir / f"{wallet_name}.json" - ctx.obj["config"] = config - - -@wallet.command() -@click.argument("name") -@click.option("--type", "wallet_type", default="hd", help="Wallet type (hd, simple)") -@click.option( - "--no-encrypt", is_flag=True, help="Skip wallet encryption (not recommended)" -) -@click.pass_context -def create(ctx, name: str, wallet_type: str, no_encrypt: bool): - """Create a new wallet""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - # Check if using daemon mode and daemon is available - if use_daemon and not adapter.is_daemon_available(): - error("Wallet daemon is not available. Falling back to file-based wallet.") - # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=False) - ctx.obj["wallet_adapter"] = adapter - - # Get password for encryption - password = None - if not no_encrypt: - if use_daemon: - # For daemon mode, use a default password or prompt - password = getpass.getpass(f"Enter password for wallet '{name}' (press Enter for default): ") - if not password: - password = "default_wallet_password" - else: - # For file mode, use existing password prompt logic - password = getpass.getpass(f"Enter password for wallet '{name}': ") - confirm = getpass.getpass("Confirm password: ") - if password != confirm: - error("Passwords do not match") - return - - # Create wallet using the adapter - try: - metadata = { - "wallet_type": wallet_type, - "created_by": "aitbc_cli", - "encryption_enabled": not no_encrypt - } - - wallet_info = adapter.create_wallet(name, password, wallet_type, metadata) - - # Display results - output(wallet_info, ctx.obj.get("output_format", "table")) - - # Set as active wallet if successful - if wallet_info: - config_file = Path.home() / ".aitbc" / "config.yaml" - config_data = {} - if config_file.exists(): - with open(config_file, "r") as f: - config_data = yaml.safe_load(f) or {} - - config_data["active_wallet"] = name - config_file.parent.mkdir(parents=True, exist_ok=True) - with open(config_file, "w") as f: - yaml.dump(config_data, f) - - success(f"Wallet '{name}' is now active") - - except Exception as e: - error(f"Failed to create wallet: {str(e)}") - return - - -@wallet.command() -@click.pass_context -def list(ctx): - """List all wallets""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - # Check if using daemon mode and daemon is available - if use_daemon and not adapter.is_daemon_available(): - error("Wallet daemon is not available. Falling back to file-based wallet listing.") - # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=False) - - try: - wallets = adapter.list_wallets() - - if not wallets: - output({"wallets": [], "count": 0, "mode": "daemon" if use_daemon else "file"}, - ctx.obj.get("output_format", "table")) - return - - # Format output - wallet_list = [] - for wallet in wallets: - wallet_info = { - "name": wallet.get("wallet_name"), - "address": wallet.get("address"), - "balance": wallet.get("balance", 0.0), - "type": wallet.get("wallet_type", "hd"), - "created_at": wallet.get("created_at"), - "mode": wallet.get("mode", "file") - } - wallet_list.append(wallet_info) - - output_data = { - "wallets": wallet_list, - "count": len(wallet_list), - "mode": "daemon" if use_daemon else "file" - } - - output(output_data, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to list wallets: {str(e)}") - - - - -@wallet.command() -@click.argument("name") -@click.pass_context -def switch(ctx, name: str): - """Switch to a different wallet""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - # Check if using daemon mode and daemon is available - if use_daemon and not adapter.is_daemon_available(): - error("Wallet daemon is not available. Falling back to file-based wallet switching.") - # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=False) - - # Check if wallet exists - wallet_info = adapter.get_wallet_info(name) - if not wallet_info: - error(f"Wallet '{name}' does not exist") - return - - # Update config - config_file = Path.home() / ".aitbc" / "config.yaml" - config = {} - if config_file.exists(): - import yaml - with open(config_file, "r") as f: - config = yaml.safe_load(f) or {} - - config["active_wallet"] = name - config_file.parent.mkdir(parents=True, exist_ok=True) - with open(config_file, "w") as f: - yaml.dump(config, f) - - success(f"Switched to wallet: {name}") - output({ - "active_wallet": name, - "mode": "daemon" if use_daemon else "file", - "wallet_info": wallet_info - }, ctx.obj.get("output_format", "table")) - - -@wallet.command() -@click.argument("name") -@click.option("--confirm", is_flag=True, help="Skip confirmation prompt") -@click.pass_context -def delete(ctx, name: str, confirm: bool): - """Delete a wallet""" - wallet_dir = ctx.obj["wallet_dir"] - wallet_path = wallet_dir / f"{name}.json" - - if not wallet_path.exists(): - error(f"Wallet '{name}' does not exist") - return - - if not confirm: - if not click.confirm( - f"Are you sure you want to delete wallet '{name}'? This cannot be undone." - ): - return - - wallet_path.unlink() - success(f"Wallet '{name}' deleted") - - # If deleted wallet was active, reset to default - config_file = Path.home() / ".aitbc" / "config.yaml" - if config_file.exists(): - import yaml - - with open(config_file, "r") as f: - config = yaml.safe_load(f) or {} - - if config.get("active_wallet") == name: - config["active_wallet"] = "default" - with open(config_file, "w") as f: - yaml.dump(config, f, default_flow_style=False) - - -@wallet.command() -@click.argument("name") -@click.option("--destination", help="Destination path for backup file") -@click.pass_context -def backup(ctx, name: str, destination: Optional[str]): - """Backup a wallet""" - wallet_dir = ctx.obj["wallet_dir"] - wallet_path = wallet_dir / f"{name}.json" - - if not wallet_path.exists(): - error(f"Wallet '{name}' does not exist") - return - - if not destination: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - destination = f"{name}_backup_{timestamp}.json" - - # Copy wallet file - shutil.copy2(wallet_path, destination) - success(f"Wallet '{name}' backed up to '{destination}'") - output( - { - "wallet": name, - "backup_path": destination, - "timestamp": datetime.utcnow().isoformat() + "Z", - } - ) - - -@wallet.command() -@click.argument("backup_path") -@click.argument("name") -@click.option("--force", is_flag=True, help="Override existing wallet") -@click.pass_context -def restore(ctx, backup_path: str, name: str, force: bool): - """Restore a wallet from backup""" - wallet_dir = ctx.obj["wallet_dir"] - wallet_path = wallet_dir / f"{name}.json" - - if wallet_path.exists() and not force: - error(f"Wallet '{name}' already exists. Use --force to override.") - return - - if not Path(backup_path).exists(): - error(f"Backup file '{backup_path}' not found") - return - - # Load and verify backup - with open(backup_path, "r") as f: - wallet_data = json.load(f) - - # Update wallet name if needed - wallet_data["wallet_id"] = name - wallet_data["restored_at"] = datetime.utcnow().isoformat() + "Z" - - # Save restored wallet (preserve encryption state) - # If wallet was encrypted, we save it as-is (still encrypted with original password) - with open(wallet_path, "w") as f: - json.dump(wallet_data, f, indent=2) - - success(f"Wallet '{name}' restored from backup") - output( - { - "wallet": name, - "restored_from": backup_path, - "address": wallet_data["address"], - } - ) - - -@wallet.command() -@click.pass_context -def info(ctx): - """Show current wallet information""" - wallet_name = ctx.obj["wallet_name"] - wallet_path = ctx.obj["wallet_path"] - config_file = Path.home() / ".aitbc" / "config.yaml" - - if not wallet_path.exists(): - error( - f"Wallet '{wallet_name}' not found. Use 'aitbc wallet create' to create one." - ) - return - - wallet_data = _load_wallet(wallet_path, wallet_name) - - # Get active wallet from config - active_wallet = "default" - if config_file.exists(): - import yaml - - with open(config_file, "r") as f: - config = yaml.safe_load(f) - active_wallet = config.get("active_wallet", "default") - - wallet_info = { - "name": wallet_data.get("name", wallet_name), - "type": wallet_data.get("type", wallet_data.get("wallet_type", "simple")), - "address": wallet_data["address"], - "public_key": wallet_data.get("public_key", "N/A"), - "created_at": wallet_data["created_at"], - "active": wallet_data.get("name", wallet_name) == active_wallet, - "path": str(wallet_path), - } - - if "balance" in wallet_data: - wallet_info["balance"] = wallet_data["balance"] - - output(wallet_info, ctx.obj.get("output_format", "table")) - - -@wallet.command() -@click.pass_context -def balance(ctx): - """Check wallet balance""" - wallet_name = ctx.obj["wallet_name"] - wallet_path = ctx.obj["wallet_path"] - config = ctx.obj.get("config") - - # Auto-create wallet if it doesn't exist - if not wallet_path.exists(): - import secrets - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat - - # Generate proper key pair - private_key_bytes = secrets.token_bytes(32) - private_key = f"0x{private_key_bytes.hex()}" - - # Derive public key from private key - priv_key = ec.derive_private_key( - int.from_bytes(private_key_bytes, "big"), ec.SECP256K1() - ) - pub_key = priv_key.public_key() - pub_key_bytes = pub_key.public_bytes( - encoding=Encoding.X962, format=PublicFormat.UncompressedPoint - ) - public_key = f"0x{pub_key_bytes.hex()}" - - # Generate address from public key - digest = hashes.Hash(hashes.SHA256()) - digest.update(pub_key_bytes) - address_hash = digest.finalize() - address = f"aitbc1{address_hash[:20].hex()}" - - wallet_data = { - "wallet_id": wallet_name, - "type": "simple", - "address": address, - "public_key": public_key, - "private_key": private_key, - "created_at": datetime.utcnow().isoformat() + "Z", - "balance": 0.0, - "transactions": [], - } - wallet_path.parent.mkdir(parents=True, exist_ok=True) - # Auto-create with encryption - success("Creating new wallet with encryption enabled") - password = _get_wallet_password(wallet_name) - _save_wallet(wallet_path, wallet_data, password) - else: - wallet_data = _load_wallet(wallet_path, wallet_name) - - # Try to get balance from blockchain if available - if config: - try: - with httpx.Client() as client: - # Try multiple balance query methods - blockchain_balance = None - - # Method 1: Try direct balance endpoint - try: - response = client.get( - f"{config.get('coordinator_url').rstrip('/')}/rpc/getBalance/{wallet_data['address']}?chain_id=ait-devnet", - timeout=5, - ) - if response.status_code == 200: - result = response.json() - blockchain_balance = result.get("balance", 0) - except Exception: - pass - - # Method 2: Try addresses list endpoint - if blockchain_balance is None: - try: - response = client.get( - f"{config.get('coordinator_url').rstrip('/')}/rpc/addresses?chain_id=ait-devnet", - timeout=5, - ) - if response.status_code == 200: - addresses = response.json() - if isinstance(addresses, list): - for addr_info in addresses: - if addr_info.get("address") == wallet_data["address"]: - blockchain_balance = addr_info.get("balance", 0) - break - except Exception: - pass - - # Method 3: Use faucet as balance check (last resort) - if blockchain_balance is None: - try: - response = client.post( - f"{config.get('coordinator_url').rstrip('/')}/rpc/admin/mintFaucet?chain_id=ait-devnet", - json={"address": wallet_data["address"], "amount": 1}, - timeout=5, - ) - if response.status_code == 200: - result = response.json() - blockchain_balance = result.get("balance", 0) - # Subtract the 1 we just added - if blockchain_balance > 0: - blockchain_balance -= 1 - except Exception: - pass - - # If we got a blockchain balance, show it - if blockchain_balance is not None: - output( - { - "wallet": wallet_name, - "address": wallet_data["address"], - "local_balance": wallet_data.get("balance", 0), - "blockchain_balance": blockchain_balance, - "synced": wallet_data.get("balance", 0) == blockchain_balance, - "note": "Blockchain balance synced" if wallet_data.get("balance", 0) == blockchain_balance else "Local and blockchain balances differ", - }, - ctx.obj.get("output_format", "table"), - ) - return - except Exception: - pass - - # Fallback to local balance only - output( - { - "wallet": wallet_name, - "address": wallet_data["address"], - "balance": wallet_data.get("balance", 0), - "note": "Local balance (blockchain balance queries unavailable)", - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.option("--limit", type=int, default=10, help="Number of transactions to show") -@click.pass_context -def history(ctx, limit: int): - """Show transaction history""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - transactions = wallet_data.get("transactions", [])[-limit:] - - # Format transactions - formatted_txs = [] - for tx in transactions: - formatted_txs.append( - { - "type": tx["type"], - "amount": tx["amount"], - "description": tx.get("description", ""), - "timestamp": tx["timestamp"], - } - ) - - output( - { - "wallet": wallet_name, - "address": wallet_data["address"], - "transactions": formatted_txs, - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.argument("amount", type=float) -@click.argument("job_id") -@click.option("--desc", help="Description of the work") -@click.pass_context -def earn(ctx, amount: float, job_id: str, desc: Optional[str]): - """Add earnings from completed job""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - # Add transaction - transaction = { - "type": "earn", - "amount": amount, - "job_id": job_id, - "description": desc or f"Job {job_id}", - "timestamp": datetime.now().isoformat(), - } - - wallet_data["transactions"].append(transaction) - wallet_data["balance"] = wallet_data.get("balance", 0) + amount - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(wallet_path, wallet_data, password) - - success(f"Earnings added: {amount} AITBC") - output( - { - "wallet": wallet_name, - "amount": amount, - "job_id": job_id, - "new_balance": wallet_data["balance"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.argument("amount", type=float) -@click.argument("description") -@click.pass_context -def spend(ctx, amount: float, description: str): - """Spend AITBC""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - balance = wallet_data.get("balance", 0) - if balance < amount: - error(f"Insufficient balance. Available: {balance}, Required: {amount}") - ctx.exit(1) - return - - # Add transaction - transaction = { - "type": "spend", - "amount": -amount, - "description": description, - "timestamp": datetime.now().isoformat(), - } - - wallet_data["transactions"].append(transaction) - wallet_data["balance"] = balance - amount - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(wallet_path, wallet_data, password) - - success(f"Spent: {amount} AITBC") - output( - { - "wallet": wallet_name, - "amount": amount, - "description": description, - "new_balance": wallet_data["balance"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.pass_context -def address(ctx): - """Show wallet address""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - output( - {"wallet": wallet_name, "address": wallet_data["address"]}, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.argument("to_address") -@click.argument("amount", type=float) -@click.option("--description", help="Transaction description") -@click.pass_context -def send(ctx, to_address: str, amount: float, description: Optional[str]): - """Send AITBC to another address""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - wallet_name = ctx.obj["wallet_name"] - - # Check if using daemon mode and daemon is available - if use_daemon and not adapter.is_daemon_available(): - error("Wallet daemon is not available. Falling back to file-based wallet send.") - # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=False) - ctx.obj["wallet_adapter"] = adapter - - # Get password for transaction - password = getpass.getpass(f"Enter password for wallet '{wallet_name}': ") - - try: - result = adapter.send_transaction(wallet_name, password, to_address, amount, description) - - # Display results - output(result, ctx.obj.get("output_format", "table")) - - # Update active wallet if successful - if result: - success(f"Transaction sent successfully") - - except Exception as e: - error(f"Failed to send transaction: {str(e)}") - return - - -@wallet.command() -@click.pass_context -def balance(ctx): - """Check wallet balance""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - wallet_name = ctx.obj["wallet_name"] - - # Check if using daemon mode and daemon is available - if use_daemon and not adapter.is_daemon_available(): - error("Wallet daemon is not available. Falling back to file-based wallet balance.") - # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - config = get_config() - adapter = DualModeWalletAdapter(config, use_daemon=False) - ctx.obj["wallet_adapter"] = adapter - - try: - balance = adapter.get_wallet_balance(wallet_name) - wallet_info = adapter.get_wallet_info(wallet_name) - - if balance is None: - error(f"Wallet '{wallet_name}' not found") - return - - output_data = { - "wallet_name": wallet_name, - "balance": balance, - "address": wallet_info.get("address") if wallet_info else None, - "mode": "daemon" if use_daemon else "file" - } - - output(output_data, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to get wallet balance: {str(e)}") - - -@wallet.group() -def daemon(): - """Wallet daemon management commands""" - pass - - -@daemon.command() -@click.pass_context -def status(ctx): - """Check wallet daemon status""" - from ..config import get_config - from ..wallet_daemon_client import WalletDaemonClient - - config = get_config() - client = WalletDaemonClient(config) - - if client.is_available(): - status_info = client.get_status() - success("Wallet daemon is available") - output(status_info, ctx.obj.get("output_format", "table")) - else: - error("Wallet daemon is not available") - output({ - "status": "unavailable", - "wallet_url": config.wallet_url, - "suggestion": "Start the wallet daemon or check the configuration" - }, ctx.obj.get("output_format", "table")) - - -@daemon.command() -@click.pass_context -def configure(ctx): - """Configure wallet daemon settings""" - from ..config import get_config - - config = get_config() - - output({ - "wallet_url": config.wallet_url, - "timeout": getattr(config, 'timeout', 30), - "suggestion": "Use AITBC_WALLET_URL environment variable or config file to change settings" - }, ctx.obj.get("output_format", "table")) - - -@wallet.command() -@click.argument("wallet_name") -@click.option("--password", help="Wallet password") -@click.option("--new-password", help="New password for daemon wallet") -@click.option("--force", is_flag=True, help="Force migration even if wallet exists") -@click.pass_context -def migrate_to_daemon(ctx, wallet_name: str, password: Optional[str], new_password: Optional[str], force: bool): - """Migrate a file-based wallet to daemon storage""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config - - config = get_config() - migration_service = WalletMigrationService(config) - - if not migration_service.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - result = migration_service.migrate_to_daemon(wallet_name, password, new_password, force) - success(f"Migrated wallet '{wallet_name}' to daemon") - output(result, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to migrate wallet: {str(e)}") - - -@wallet.command() -@click.argument("wallet_name") -@click.option("--password", help="Wallet password") -@click.option("--new-password", help="New password for file wallet") -@click.option("--force", is_flag=True, help="Force migration even if wallet exists") -@click.pass_context -def migrate_to_file(ctx, wallet_name: str, password: Optional[str], new_password: Optional[str], force: bool): - """Migrate a daemon-based wallet to file storage""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config - - config = get_config() - migration_service = WalletMigrationService(config) - - if not migration_service.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - result = migration_service.migrate_to_file(wallet_name, password, new_password, force) - success(f"Migrated wallet '{wallet_name}' to file storage") - output(result, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to migrate wallet: {str(e)}") - - -@wallet.command() -@click.pass_context -def migration_status(ctx): - """Show wallet migration status""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config - - config = get_config() - migration_service = WalletMigrationService(config) - - try: - status = migration_service.get_migration_status() - output(status, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to get migration status: {str(e)}") -def stats(ctx): - """Show wallet statistics""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - transactions = wallet_data.get("transactions", []) - - # Calculate stats - total_earned = sum( - tx["amount"] for tx in transactions if tx["type"] == "earn" and tx["amount"] > 0 - ) - total_spent = sum( - abs(tx["amount"]) - for tx in transactions - if tx["type"] in ["spend", "send"] and tx["amount"] < 0 - ) - jobs_completed = len([tx for tx in transactions if tx["type"] == "earn"]) - - output( - { - "wallet": wallet_name, - "address": wallet_data["address"], - "current_balance": wallet_data.get("balance", 0), - "total_earned": total_earned, - "total_spent": total_spent, - "jobs_completed": jobs_completed, - "transaction_count": len(transactions), - "wallet_created": wallet_data.get("created_at"), - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.argument("amount", type=float) -@click.option("--duration", type=int, default=30, help="Staking duration in days") -@click.pass_context -def stake(ctx, amount: float, duration: int): - """Stake AITBC tokens""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - balance = wallet_data.get("balance", 0) - if balance < amount: - error(f"Insufficient balance. Available: {balance}, Required: {amount}") - ctx.exit(1) - return - - # Record stake - stake_id = f"stake_{int(datetime.now().timestamp())}" - stake_record = { - "stake_id": stake_id, - "amount": amount, - "duration_days": duration, - "start_date": datetime.now().isoformat(), - "end_date": (datetime.now() + timedelta(days=duration)).isoformat(), - "status": "active", - "apy": 5.0 + (duration / 30) * 1.5, # Higher APY for longer stakes - } - - staking = wallet_data.setdefault("staking", []) - staking.append(stake_record) - wallet_data["balance"] = balance - amount - - # Add transaction - wallet_data["transactions"].append( - { - "type": "stake", - "amount": -amount, - "stake_id": stake_id, - "description": f"Staked {amount} AITBC for {duration} days", - "timestamp": datetime.now().isoformat(), - } - ) - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(wallet_path, wallet_data, password) - - success(f"Staked {amount} AITBC for {duration} days") - output( - { - "wallet": wallet_name, - "stake_id": stake_id, - "amount": amount, - "duration_days": duration, - "apy": stake_record["apy"], - "new_balance": wallet_data["balance"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.argument("stake_id") -@click.pass_context -def unstake(ctx, stake_id: str): - """Unstake AITBC tokens""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - staking = wallet_data.get("staking", []) - stake_record = next( - (s for s in staking if s["stake_id"] == stake_id and s["status"] == "active"), - None, - ) - - if not stake_record: - error(f"Active stake '{stake_id}' not found") - ctx.exit(1) - return - - # Calculate rewards - start = datetime.fromisoformat(stake_record["start_date"]) - days_staked = max(1, (datetime.now() - start).days) - daily_rate = stake_record["apy"] / 100 / 365 - rewards = stake_record["amount"] * daily_rate * days_staked - - # Return principal + rewards - returned = stake_record["amount"] + rewards - wallet_data["balance"] = wallet_data.get("balance", 0) + returned - stake_record["status"] = "completed" - stake_record["rewards"] = rewards - stake_record["completed_date"] = datetime.now().isoformat() - - # Add transaction - wallet_data["transactions"].append( - { - "type": "unstake", - "amount": returned, - "stake_id": stake_id, - "rewards": rewards, - "description": f"Unstaked {stake_record['amount']} AITBC + {rewards:.4f} rewards", - "timestamp": datetime.now().isoformat(), - } - ) - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(wallet_path, wallet_data, password) - - success(f"Unstaked {stake_record['amount']} AITBC + {rewards:.4f} rewards") - output( - { - "wallet": wallet_name, - "stake_id": stake_id, - "principal": stake_record["amount"], - "rewards": rewards, - "total_returned": returned, - "days_staked": days_staked, - "new_balance": wallet_data["balance"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="staking-info") -@click.pass_context -def staking_info(ctx): - """Show staking information""" - 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 - - wallet_data = _load_wallet(wallet_path, wallet_name) - - staking = wallet_data.get("staking", []) - active_stakes = [s for s in staking if s["status"] == "active"] - completed_stakes = [s for s in staking if s["status"] == "completed"] - - total_staked = sum(s["amount"] for s in active_stakes) - total_rewards = sum(s.get("rewards", 0) for s in completed_stakes) - - output( - { - "wallet": wallet_name, - "total_staked": total_staked, - "total_rewards_earned": total_rewards, - "active_stakes": len(active_stakes), - "completed_stakes": len(completed_stakes), - "stakes": [ - { - "stake_id": s["stake_id"], - "amount": s["amount"], - "apy": s["apy"], - "duration_days": s["duration_days"], - "status": s["status"], - "start_date": s["start_date"], - } - for s in staking - ], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="multisig-create") -@click.argument("signers", nargs=-1, required=True) -@click.option( - "--threshold", type=int, required=True, help="Required signatures to approve" -) -@click.option("--name", required=True, help="Multisig wallet name") -@click.pass_context -def multisig_create(ctx, signers: tuple, threshold: int, name: str): - """Create a multi-signature wallet""" - wallet_dir = ctx.obj.get("wallet_dir", Path.home() / ".aitbc" / "wallets") - wallet_dir.mkdir(parents=True, exist_ok=True) - multisig_path = wallet_dir / f"{name}_multisig.json" - - if multisig_path.exists(): - error(f"Multisig wallet '{name}' already exists") - return - - if threshold > len(signers): - error( - f"Threshold ({threshold}) cannot exceed number of signers ({len(signers)})" - ) - return - - import secrets - - multisig_data = { - "wallet_id": name, - "type": "multisig", - "address": f"aitbc1ms{secrets.token_hex(18)}", - "signers": list(signers), - "threshold": threshold, - "created_at": datetime.now().isoformat(), - "balance": 0.0, - "transactions": [], - "pending_transactions": [], - } - - with open(multisig_path, "w") as f: - json.dump(multisig_data, f, indent=2) - - success(f"Multisig wallet '{name}' created ({threshold}-of-{len(signers)})") - output( - { - "name": name, - "address": multisig_data["address"], - "signers": list(signers), - "threshold": threshold, - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="multisig-propose") -@click.option("--wallet", "wallet_name", required=True, help="Multisig wallet name") -@click.argument("to_address") -@click.argument("amount", type=float) -@click.option("--description", help="Transaction description") -@click.pass_context -def multisig_propose( - ctx, wallet_name: str, to_address: str, amount: float, description: Optional[str] -): - """Propose a multisig transaction""" - wallet_dir = ctx.obj.get("wallet_dir", Path.home() / ".aitbc" / "wallets") - multisig_path = wallet_dir / f"{wallet_name}_multisig.json" - - if not multisig_path.exists(): - error(f"Multisig wallet '{wallet_name}' not found") - return - - with open(multisig_path) as f: - ms_data = json.load(f) - - if ms_data.get("balance", 0) < amount: - error( - f"Insufficient balance. Available: {ms_data['balance']}, Required: {amount}" - ) - ctx.exit(1) - return - - import secrets - - tx_id = f"mstx_{secrets.token_hex(8)}" - pending_tx = { - "tx_id": tx_id, - "to": to_address, - "amount": amount, - "description": description or "", - "proposed_at": datetime.now().isoformat(), - "proposed_by": os.environ.get("USER", "unknown"), - "signatures": [], - "status": "pending", - } - - ms_data.setdefault("pending_transactions", []).append(pending_tx) - with open(multisig_path, "w") as f: - json.dump(ms_data, f, indent=2) - - success(f"Transaction proposed: {tx_id}") - output( - { - "tx_id": tx_id, - "to": to_address, - "amount": amount, - "signatures_needed": ms_data["threshold"], - "status": "pending", - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="multisig-challenge") -@click.option("--wallet", "wallet_name", required=True, help="Multisig wallet name") -@click.argument("tx_id") -@click.pass_context -def multisig_challenge(ctx, wallet_name: str, tx_id: str): - """Create a cryptographic challenge for multisig transaction signing""" - wallet_dir = ctx.obj.get("wallet_dir", Path.home() / ".aitbc" / "wallets") - multisig_path = wallet_dir / f"{wallet_name}_multisig.json" - - if not multisig_path.exists(): - error(f"Multisig wallet '{wallet_name}' not found") - return - - with open(multisig_path) as f: - ms_data = json.load(f) - - # Find pending transaction - pending = ms_data.get("pending_transactions", []) - tx = next( - (t for t in pending if t["tx_id"] == tx_id and t["status"] == "pending"), None - ) - - if not tx: - error(f"Pending transaction '{tx_id}' not found") - return - - # Import crypto utilities - from ..utils.crypto_utils import multisig_security - - try: - # Create signing request - signing_request = multisig_security.create_signing_request(tx, wallet_name) - - output({ - "tx_id": tx_id, - "wallet": wallet_name, - "challenge": signing_request["challenge"], - "nonce": signing_request["nonce"], - "message": signing_request["message"], - "instructions": [ - "1. Copy the challenge string above", - "2. Sign it with your private key using: aitbc wallet sign-challenge ", - "3. Use the returned signature with: aitbc wallet multisig-sign --wallet --signer
--signature " - ] - }, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to create challenge: {e}") - - -@wallet.command(name="sign-challenge") -@click.argument("challenge") -@click.argument("private_key") -@click.pass_context -def sign_challenge(ctx, challenge: str, private_key: str): - """Sign a cryptographic challenge (for testing multisig)""" - from ..utils.crypto_utils import sign_challenge - - try: - signature = sign_challenge(challenge, private_key) - - output({ - "challenge": challenge, - "signature": signature, - "message": "Use this signature with multisig-sign command" - }, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to sign challenge: {e}") - - -@wallet.command(name="multisig-sign") -@click.option("--wallet", "wallet_name", required=True, help="Multisig wallet name") -@click.argument("tx_id") -@click.option("--signer", required=True, help="Signer address") -@click.option("--signature", required=True, help="Cryptographic signature (hex)") -@click.pass_context -def multisig_sign(ctx, wallet_name: str, tx_id: str, signer: str, signature: str): - """Sign a pending multisig transaction with cryptographic verification""" - wallet_dir = ctx.obj.get("wallet_dir", Path.home() / ".aitbc" / "wallets") - multisig_path = wallet_dir / f"{wallet_name}_multisig.json" - - if not multisig_path.exists(): - error(f"Multisig wallet '{wallet_name}' not found") - return - - with open(multisig_path) as f: - ms_data = json.load(f) - - if signer not in ms_data.get("signers", []): - error(f"'{signer}' is not an authorized signer") - ctx.exit(1) - return - - # Import crypto utilities - from ..utils.crypto_utils import multisig_security - - # Verify signature cryptographically - success, message = multisig_security.verify_and_add_signature(tx_id, signature, signer) - if not success: - error(f"Signature verification failed: {message}") - ctx.exit(1) - return - - pending = ms_data.get("pending_transactions", []) - tx = next( - (t for t in pending if t["tx_id"] == tx_id and t["status"] == "pending"), None - ) - - if not tx: - error(f"Pending transaction '{tx_id}' not found") - ctx.exit(1) - return - - # Check if already signed - for sig in tx.get("signatures", []): - if sig["signer"] == signer: - error(f"'{signer}' has already signed this transaction") - return - - # Add cryptographic signature - if "signatures" not in tx: - tx["signatures"] = [] - - tx["signatures"].append({ - "signer": signer, - "signature": signature, - "timestamp": datetime.now().isoformat() - }) - - # Check if threshold met - if len(tx["signatures"]) >= ms_data["threshold"]: - tx["status"] = "approved" - # Execute the transaction - ms_data["balance"] = ms_data.get("balance", 0) - tx["amount"] - ms_data["transactions"].append( - { - "type": "multisig_send", - "amount": -tx["amount"], - "to": tx["to"], - "tx_id": tx["tx_id"], - "signatures": tx["signatures"], - "timestamp": datetime.now().isoformat(), - } - ) - success(f"Transaction {tx_id} approved and executed!") - else: - success( - f"Signed. {len(tx['signatures'])}/{ms_data['threshold']} signatures collected" - ) - - with open(multisig_path, "w") as f: - json.dump(ms_data, f, indent=2) - - output( - { - "tx_id": tx_id, - "signatures": tx["signatures"], - "threshold": ms_data["threshold"], - "status": tx["status"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="liquidity-stake") -@click.argument("amount", type=float) -@click.option("--pool", default="main", help="Liquidity pool name") -@click.option( - "--lock-days", type=int, default=0, help="Lock period in days (higher APY)" -) -@click.pass_context -def liquidity_stake(ctx, amount: float, pool: str, lock_days: int): - """Stake tokens into a liquidity pool""" - wallet_name = ctx.obj["wallet_name"] - wallet_path = ctx.obj.get("wallet_path") - if not wallet_path or not Path(wallet_path).exists(): - error("Wallet not found") - ctx.exit(1) - return - - wallet_data = _load_wallet(Path(wallet_path), wallet_name) - - balance = wallet_data.get("balance", 0) - if balance < amount: - error(f"Insufficient balance. Available: {balance}, Required: {amount}") - ctx.exit(1) - return - - # APY tiers based on lock period - if lock_days >= 90: - apy = 12.0 - tier = "platinum" - elif lock_days >= 30: - apy = 8.0 - tier = "gold" - elif lock_days >= 7: - apy = 5.0 - tier = "silver" - else: - apy = 3.0 - tier = "bronze" - - import secrets - - stake_id = f"liq_{secrets.token_hex(6)}" - now = datetime.now() - - liq_record = { - "stake_id": stake_id, - "pool": pool, - "amount": amount, - "apy": apy, - "tier": tier, - "lock_days": lock_days, - "start_date": now.isoformat(), - "unlock_date": (now + timedelta(days=lock_days)).isoformat() - if lock_days > 0 - else None, - "status": "active", - } - - wallet_data.setdefault("liquidity", []).append(liq_record) - wallet_data["balance"] = balance - amount - - wallet_data["transactions"].append( - { - "type": "liquidity_stake", - "amount": -amount, - "pool": pool, - "stake_id": stake_id, - "timestamp": now.isoformat(), - } - ) - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(Path(wallet_path), wallet_data, password) - - success(f"Staked {amount} AITBC into '{pool}' pool ({tier} tier, {apy}% APY)") - output( - { - "stake_id": stake_id, - "pool": pool, - "amount": amount, - "apy": apy, - "tier": tier, - "lock_days": lock_days, - "new_balance": wallet_data["balance"], - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command(name="liquidity-unstake") -@click.argument("stake_id") -@click.pass_context -def liquidity_unstake(ctx, stake_id: str): - """Withdraw from a liquidity pool with rewards""" - wallet_name = ctx.obj["wallet_name"] - wallet_path = ctx.obj.get("wallet_path") - if not wallet_path or not Path(wallet_path).exists(): - error("Wallet not found") - ctx.exit(1) - return - - wallet_data = _load_wallet(Path(wallet_path), wallet_name) - - liquidity = wallet_data.get("liquidity", []) - record = next( - (r for r in liquidity if r["stake_id"] == stake_id and r["status"] == "active"), - None, - ) - - if not record: - error(f"Active liquidity stake '{stake_id}' not found") - ctx.exit(1) - return - - # Check lock period - if record.get("unlock_date"): - unlock = datetime.fromisoformat(record["unlock_date"]) - if datetime.now() < unlock: - error(f"Stake is locked until {record['unlock_date']}") - ctx.exit(1) - return - - # Calculate rewards - start = datetime.fromisoformat(record["start_date"]) - days_staked = max((datetime.now() - start).total_seconds() / 86400, 0.001) - rewards = record["amount"] * (record["apy"] / 100) * (days_staked / 365) - total = record["amount"] + rewards - - record["status"] = "completed" - record["end_date"] = datetime.now().isoformat() - record["rewards"] = round(rewards, 6) - - wallet_data["balance"] = wallet_data.get("balance", 0) + total - - wallet_data["transactions"].append( - { - "type": "liquidity_unstake", - "amount": total, - "principal": record["amount"], - "rewards": round(rewards, 6), - "pool": record["pool"], - "stake_id": stake_id, - "timestamp": datetime.now().isoformat(), - } - ) - - # Save wallet with encryption - password = None - if wallet_data.get("encrypted"): - password = _get_wallet_password(wallet_name) - _save_wallet(Path(wallet_path), wallet_data, password) - - success( - f"Withdrawn {total:.6f} AITBC (principal: {record['amount']}, rewards: {rewards:.6f})" - ) - output( - { - "stake_id": stake_id, - "pool": record["pool"], - "principal": record["amount"], - "rewards": round(rewards, 6), - "total_returned": round(total, 6), - "days_staked": round(days_staked, 2), - "apy": record["apy"], - "new_balance": round(wallet_data["balance"], 6), - }, - ctx.obj.get("output_format", "table"), - ) - - -@wallet.command() -@click.pass_context -def rewards(ctx): - """View all earned rewards (staking + liquidity)""" - wallet_name = ctx.obj["wallet_name"] - wallet_path = ctx.obj.get("wallet_path") - if not wallet_path or not Path(wallet_path).exists(): - error("Wallet not found") - ctx.exit(1) - return - - wallet_data = _load_wallet(Path(wallet_path), wallet_name) - - staking = wallet_data.get("staking", []) - liquidity = wallet_data.get("liquidity", []) - - # Staking rewards - staking_rewards = sum( - s.get("rewards", 0) for s in staking if s.get("status") == "completed" - ) - active_staking = sum(s["amount"] for s in staking if s.get("status") == "active") - - # Liquidity rewards - liq_rewards = sum( - r.get("rewards", 0) for r in liquidity if r.get("status") == "completed" - ) - active_liquidity = sum( - r["amount"] for r in liquidity if r.get("status") == "active" - ) - - # Estimate pending rewards for active positions - pending_staking = 0 - for s in staking: - if s.get("status") == "active": - start = datetime.fromisoformat(s["start_date"]) - days = max((datetime.now() - start).total_seconds() / 86400, 0) - pending_staking += s["amount"] * (s["apy"] / 100) * (days / 365) - - pending_liquidity = 0 - for r in liquidity: - if r.get("status") == "active": - start = datetime.fromisoformat(r["start_date"]) - days = max((datetime.now() - start).total_seconds() / 86400, 0) - pending_liquidity += r["amount"] * (r["apy"] / 100) * (days / 365) - - output( - { - "staking_rewards_earned": round(staking_rewards, 6), - "staking_rewards_pending": round(pending_staking, 6), - "staking_active_amount": active_staking, - "liquidity_rewards_earned": round(liq_rewards, 6), - "liquidity_rewards_pending": round(pending_liquidity, 6), - "liquidity_active_amount": active_liquidity, - "total_earned": round(staking_rewards + liq_rewards, 6), - "total_pending": round(pending_staking + pending_liquidity, 6), - "total_staked": active_staking + active_liquidity, - }, - ctx.obj.get("output_format", "table"), - ) - - -# Multi-Chain Commands - -@wallet.group() -def chain(): - """Multi-chain wallet operations""" - pass - - -@chain.command() -@click.pass_context -def list(ctx): - """List all blockchain chains""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - chains = adapter.list_chains() - output({ - "chains": chains, - "count": len(chains), - "mode": "daemon" - }, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to list chains: {str(e)}") - - -@chain.command() -@click.argument("chain_id") -@click.argument("name") -@click.argument("coordinator_url") -@click.argument("coordinator_api_key") -@click.pass_context -def create(ctx, chain_id: str, name: str, coordinator_url: str, coordinator_api_key: str): - """Create a new blockchain chain""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - chain = adapter.create_chain(chain_id, name, coordinator_url, coordinator_api_key) - if chain: - success(f"Created chain: {chain_id}") - output(chain, ctx.obj.get("output_format", "table")) - else: - error(f"Failed to create chain: {chain_id}") - - except Exception as e: - error(f"Failed to create chain: {str(e)}") - - -@chain.command() -@click.pass_context -def status(ctx): - """Get chain status and statistics""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - status = adapter.get_chain_status() - output(status, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to get chain status: {str(e)}") - - -@chain.command() -@click.argument("chain_id") -@click.pass_context -def wallets(ctx, chain_id: str): - """List wallets in a specific chain""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - wallets = adapter.list_wallets_in_chain(chain_id) - output({ - "chain_id": chain_id, - "wallets": wallets, - "count": len(wallets), - "mode": "daemon" - }, ctx.obj.get("output_format", "table")) - - except Exception as e: - error(f"Failed to list wallets in chain {chain_id}: {str(e)}") - - -@chain.command() -@click.argument("chain_id") -@click.argument("wallet_name") -@click.pass_context -def info(ctx, chain_id: str, wallet_name: str): - """Get wallet information from a specific chain""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - wallet_info = adapter.get_wallet_info_in_chain(chain_id, wallet_name) - if wallet_info: - output(wallet_info, ctx.obj.get("output_format", "table")) - else: - error(f"Wallet '{wallet_name}' not found in chain '{chain_id}'") - - except Exception as e: - error(f"Failed to get wallet info: {str(e)}") - - -@chain.command() -@click.argument("chain_id") -@click.argument("wallet_name") -@click.pass_context -def balance(ctx, chain_id: str, wallet_name: str): - """Get wallet balance in a specific chain""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - balance = adapter.get_wallet_balance_in_chain(chain_id, wallet_name) - if balance is not None: - output({ - "chain_id": chain_id, - "wallet_name": wallet_name, - "balance": balance, - "mode": "daemon" - }, ctx.obj.get("output_format", "table")) - else: - error(f"Could not get balance for wallet '{wallet_name}' in chain '{chain_id}'") - - except Exception as e: - error(f"Failed to get wallet balance: {str(e)}") - - -@chain.command() -@click.argument("source_chain_id") -@click.argument("target_chain_id") -@click.argument("wallet_name") -@click.option("--new-password", help="New password for target chain wallet") -@click.pass_context -def migrate(ctx, source_chain_id: str, target_chain_id: str, wallet_name: str, new_password: Optional[str]): - """Migrate a wallet from one chain to another""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - # Get password - import getpass - password = getpass.getpass(f"Enter password for wallet '{wallet_name}': ") - - result = adapter.migrate_wallet(source_chain_id, target_chain_id, wallet_name, password, new_password) - if result: - success(f"Migrated wallet '{wallet_name}' from '{source_chain_id}' to '{target_chain_id}'") - output(result, ctx.obj.get("output_format", "table")) - else: - error(f"Failed to migrate wallet '{wallet_name}'") - - except Exception as e: - error(f"Failed to migrate wallet: {str(e)}") - - -@wallet.command() -@click.argument("chain_id") -@click.argument("wallet_name") -@click.option("--type", "wallet_type", default="hd", help="Wallet type (hd, simple)") -@click.option("--no-encrypt", is_flag=True, help="Skip wallet encryption (not recommended)") -@click.pass_context -def create_in_chain(ctx, chain_id: str, wallet_name: str, wallet_type: str, no_encrypt: bool): - """Create a wallet in a specific chain""" - adapter = ctx.obj["wallet_adapter"] - use_daemon = ctx.obj["use_daemon"] - - if not use_daemon: - error("Chain operations require daemon mode. Use --use-daemon flag.") - return - - if not adapter.is_daemon_available(): - error("Wallet daemon is not available") - return - - try: - # Get password - import getpass - if not no_encrypt: - password = getpass.getpass(f"Enter password for wallet '{wallet_name}': ") - confirm_password = getpass.getpass(f"Confirm password for wallet '{wallet_name}': ") - if password != confirm_password: - error("Passwords do not match") - return - else: - password = "insecure" # Default password for unencrypted wallets - - metadata = { - "wallet_type": wallet_type, - "encrypted": not no_encrypt, - "created_at": datetime.now().isoformat() - } - - result = adapter.create_wallet_in_chain(chain_id, wallet_name, password, wallet_type, metadata) - if result: - success(f"Created wallet '{wallet_name}' in chain '{chain_id}'") - output(result, ctx.obj.get("output_format", "table")) - else: - error(f"Failed to create wallet '{wallet_name}' in chain '{chain_id}'") - - except Exception as e: - error(f"Failed to create wallet in chain: {str(e)}") - - -@wallet.command() -@click.option("--threshold", type=int, required=True, help="Number of signatures required") -@click.option("--signers", multiple=True, required=True, help="Public keys of signers") -@click.option("--wallet-name", help="Name for the multi-sig wallet") -@click.option("--chain-id", help="Chain ID for multi-chain support") -@click.pass_context -def multisig_create(ctx, threshold: int, signers: tuple, wallet_name: Optional[str], chain_id: Optional[str]): - """Create a multi-signature wallet""" - config = ctx.obj.get('config') - - if len(signers) < threshold: - error(f"Threshold {threshold} cannot be greater than number of signers {len(signers)}") - return - - multisig_data = { - "threshold": threshold, - "signers": list(signers), - "wallet_name": wallet_name or f"multisig_{int(datetime.now().timestamp())}", - "created_at": datetime.utcnow().isoformat() - } - - if chain_id: - multisig_data["chain_id"] = chain_id - - try: - if ctx.obj.get("use_daemon"): - # Use wallet daemon for multi-sig creation - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - adapter = DualModeWalletAdapter(config) - - result = adapter.create_multisig_wallet( - threshold=threshold, - signers=list(signers), - wallet_name=wallet_name, - chain_id=chain_id - ) - - if result: - success(f"Multi-sig wallet '{multisig_data['wallet_name']}' created!") - success(f"Threshold: {threshold}/{len(signers)}") - success(f"Signers: {len(signers)}") - output(result, ctx.obj.get('output_format', 'table')) - else: - error("Failed to create multi-sig wallet") - else: - # Local multi-sig wallet creation - wallet_dir = Path.home() / ".aitbc" / "wallets" - wallet_dir.mkdir(parents=True, exist_ok=True) - - wallet_file = wallet_dir / f"{multisig_data['wallet_name']}.json" - - if wallet_file.exists(): - error(f"Wallet '{multisig_data['wallet_name']}' already exists") - return - - # Save multi-sig wallet - with open(wallet_file, 'w') as f: - json.dump(multisig_data, f, indent=2) - - success(f"Multi-sig wallet '{multisig_data['wallet_name']}' created!") - success(f"Threshold: {threshold}/{len(signers)}") - output(multisig_data, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to create multi-sig wallet: {e}") - - -@wallet.command() -@click.option("--amount", type=float, required=True, help="Transfer limit amount") -@click.option("--period", default="daily", help="Limit period (hourly, daily, weekly)") -@click.option("--wallet-name", help="Wallet to set limit for") -@click.pass_context -def set_limit(ctx, amount: float, period: str, wallet_name: Optional[str]): - """Set transfer limits for wallet""" - config = ctx.obj.get('config') - - limit_data = { - "amount": amount, - "period": period, - "set_at": datetime.utcnow().isoformat() - } - - try: - if ctx.obj.get("use_daemon"): - # Use wallet daemon - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - adapter = DualModeWalletAdapter(config) - - result = adapter.set_transfer_limit( - amount=amount, - period=period, - wallet_name=wallet_name - ) - - if result: - success(f"Transfer limit set: {amount} {period}") - output(result, ctx.obj.get('output_format', 'table')) - else: - error("Failed to set transfer limit") - else: - # Local limit setting - limits_file = Path.home() / ".aitbc" / "transfer_limits.json" - limits_file.parent.mkdir(parents=True, exist_ok=True) - - # Load existing limits - limits = {} - if limits_file.exists(): - with open(limits_file, 'r') as f: - limits = json.load(f) - - # Set new limit - wallet_key = wallet_name or "default" - limits[wallet_key] = limit_data - - # Save limits - with open(limits_file, 'w') as f: - json.dump(limits, f, indent=2) - - success(f"Transfer limit set for '{wallet_key}': {amount} {period}") - output(limit_data, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to set transfer limit: {e}") - - -@wallet.command() -@click.option("--amount", type=float, required=True, help="Amount to time-lock") -@click.option("--duration", type=int, required=True, help="Lock duration in hours") -@click.option("--recipient", required=True, help="Recipient address") -@click.option("--wallet-name", help="Wallet to create time-lock from") -@click.pass_context -def time_lock(ctx, amount: float, duration: int, recipient: str, wallet_name: Optional[str]): - """Create a time-locked transfer""" - config = ctx.obj.get('config') - - lock_data = { - "amount": amount, - "duration_hours": duration, - "recipient": recipient, - "wallet_name": wallet_name or "default", - "created_at": datetime.utcnow().isoformat(), - "unlock_time": (datetime.utcnow() + timedelta(hours=duration)).isoformat() - } - - try: - if ctx.obj.get("use_daemon"): - # Use wallet daemon - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - adapter = DualModeWalletAdapter(config) - - result = adapter.create_time_lock( - amount=amount, - duration_hours=duration, - recipient=recipient, - wallet_name=wallet_name - ) - - if result: - success(f"Time-locked transfer created: {amount} tokens") - success(f"Unlocks in: {duration} hours") - success(f"Recipient: {recipient}") - output(result, ctx.obj.get('output_format', 'table')) - else: - error("Failed to create time-lock") - else: - # Local time-lock creation - locks_file = Path.home() / ".aitbc" / "time_locks.json" - locks_file.parent.mkdir(parents=True, exist_ok=True) - - # Load existing locks - locks = [] - if locks_file.exists(): - with open(locks_file, 'r') as f: - locks = json.load(f) - - # Add new lock - locks.append(lock_data) - - # Save locks - with open(locks_file, 'w') as f: - json.dump(locks, f, indent=2) - - success(f"Time-locked transfer created: {amount} tokens") - success(f"Unlocks at: {lock_data['unlock_time']}") - success(f"Recipient: {recipient}") - output(lock_data, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to create time-lock: {e}") - - -@wallet.command() -@click.option("--wallet-name", help="Wallet to check limits for") -@click.pass_context -def check_limits(ctx, wallet_name: Optional[str]): - """Check transfer limits for wallet""" - limits_file = Path.home() / ".aitbc" / "transfer_limits.json" - - if not limits_file.exists(): - error("No transfer limits configured") - return - - try: - with open(limits_file, 'r') as f: - limits = json.load(f) - - wallet_key = wallet_name or "default" - - if wallet_key not in limits: - error(f"No transfer limits configured for '{wallet_key}'") - return - - limit_info = limits[wallet_key] - success(f"Transfer limits for '{wallet_key}':") - output(limit_info, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to check transfer limits: {e}") - - -@wallet.command() -@click.option("--wallet-name", help="Wallet to check locks for") -@click.pass_context -def list_time_locks(ctx, wallet_name: Optional[str]): - """List time-locked transfers""" - locks_file = Path.home() / ".aitbc" / "time_locks.json" - - if not locks_file.exists(): - error("No time-locked transfers found") - return - - try: - with open(locks_file, 'r') as f: - locks = json.load(f) - - # Filter by wallet if specified - if wallet_name: - locks = [lock for lock in locks if lock.get('wallet_name') == wallet_name] - - if not locks: - error(f"No time-locked transfers found for '{wallet_name}'") - return - - success(f"Time-locked transfers ({len(locks)} found):") - output({"time_locks": locks}, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to list time-locks: {e}") - - -@wallet.command() -@click.option("--wallet-name", help="Wallet name for audit") -@click.option("--days", type=int, default=30, help="Number of days to audit") -@click.pass_context -def audit_trail(ctx, wallet_name: Optional[str], days: int): - """Generate wallet audit trail""" - config = ctx.obj.get('config') - - audit_data = { - "wallet_name": wallet_name or "all", - "audit_period_days": days, - "generated_at": datetime.utcnow().isoformat() - } - - try: - if ctx.obj.get("use_daemon"): - # Use wallet daemon for audit - from ..dual_mode_wallet_adapter import DualModeWalletAdapter - adapter = DualModeWalletAdapter(config) - - result = adapter.get_audit_trail( - wallet_name=wallet_name, - days=days - ) - - if result: - success(f"Audit trail for '{wallet_name or 'all wallets'}':") - output(result, ctx.obj.get('output_format', 'table')) - else: - error("Failed to generate audit trail") - else: - # Local audit trail generation - audit_file = Path.home() / ".aitbc" / "audit_trail.json" - audit_file.parent.mkdir(parents=True, exist_ok=True) - - # Generate sample audit data - cutoff_date = datetime.utcnow() - timedelta(days=days) - - audit_data["transactions"] = [] - audit_data["signatures"] = [] - audit_data["limits"] = [] - audit_data["time_locks"] = [] - - success(f"Audit trail generated for '{wallet_name or 'all wallets'}':") - output(audit_data, ctx.obj.get('output_format', 'table')) - - except Exception as e: - error(f"Failed to generate audit trail: {e}") diff --git a/cli/aitbc_cli/auth/__init__.py b/cli/auth/__init__.py similarity index 98% rename from cli/aitbc_cli/auth/__init__.py rename to cli/auth/__init__.py index fa95af90..ff6541ae 100755 --- a/cli/aitbc_cli/auth/__init__.py +++ b/cli/auth/__init__.py @@ -3,7 +3,7 @@ import keyring import os from typing import Optional, Dict -from ..utils import success, error, warning +from utils import success, error, warning class AuthManager: diff --git a/cli/aitbc_cli/commands/__init__.py b/cli/commands/__init__.py similarity index 100% rename from cli/aitbc_cli/commands/__init__.py rename to cli/commands/__init__.py diff --git a/cli/aitbc_cli/commands/admin.py b/cli/commands/admin.py similarity index 99% rename from cli/aitbc_cli/commands/admin.py rename to cli/commands/admin.py index 91a3012d..5f693729 100755 --- a/cli/aitbc_cli/commands/admin.py +++ b/cli/commands/admin.py @@ -4,7 +4,7 @@ import click import httpx import json from typing import Optional, List, Dict, Any -from ..utils import output, error, success +from utils import output, error, success @click.group() @@ -496,7 +496,7 @@ def backup(ctx): @click.pass_context def audit_log(ctx, limit: int, action_filter: Optional[str]): """View audit log""" - from ..utils import AuditLogger + from utils import AuditLogger logger = AuditLogger() entries = logger.get_logs(limit=limit, action_filter=action_filter) diff --git a/cli/aitbc_cli/commands/advanced_analytics.py b/cli/commands/advanced_analytics.py similarity index 99% rename from cli/aitbc_cli/commands/advanced_analytics.py rename to cli/commands/advanced_analytics.py index abad5fb1..45ce40fe 100755 --- a/cli/aitbc_cli/commands/advanced_analytics.py +++ b/cli/commands/advanced_analytics.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/agent.py b/cli/commands/agent.py similarity index 99% rename from cli/aitbc_cli/commands/agent.py rename to cli/commands/agent.py index b5d2adb2..6da36d8e 100755 --- a/cli/aitbc_cli/commands/agent.py +++ b/cli/commands/agent.py @@ -7,7 +7,7 @@ import time import uuid from typing import Optional, Dict, Any, List from pathlib import Path -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/agent_comm.py b/cli/commands/agent_comm.py similarity index 99% rename from cli/aitbc_cli/commands/agent_comm.py rename to cli/commands/agent_comm.py index 79f37e09..c40000fa 100755 --- a/cli/aitbc_cli/commands/agent_comm.py +++ b/cli/commands/agent_comm.py @@ -5,12 +5,12 @@ import asyncio import json from datetime import datetime, timedelta from typing import Optional -from ..core.config import load_multichain_config -from ..core.agent_communication import ( +from core.config import load_multichain_config +from core.agent_communication import ( CrossChainAgentCommunication, AgentInfo, AgentMessage, MessageType, AgentStatus ) -from ..utils import output, error, success +from utils import output, error, success @click.group() def agent_comm(): diff --git a/cli/aitbc_cli/commands/ai.py b/cli/commands/ai.py similarity index 100% rename from cli/aitbc_cli/commands/ai.py rename to cli/commands/ai.py diff --git a/cli/aitbc_cli/commands/ai_surveillance.py b/cli/commands/ai_surveillance.py similarity index 99% rename from cli/aitbc_cli/commands/ai_surveillance.py rename to cli/commands/ai_surveillance.py index 853641ed..35530c25 100755 --- a/cli/aitbc_cli/commands/ai_surveillance.py +++ b/cli/commands/ai_surveillance.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/ai_trading.py b/cli/commands/ai_trading.py similarity index 99% rename from cli/aitbc_cli/commands/ai_trading.py rename to cli/commands/ai_trading.py index aed10b30..7b95c3cc 100755 --- a/cli/aitbc_cli/commands/ai_trading.py +++ b/cli/commands/ai_trading.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/analytics.py b/cli/commands/analytics.py similarity index 99% rename from cli/aitbc_cli/commands/analytics.py rename to cli/commands/analytics.py index 64d6d8ac..958bff5a 100755 --- a/cli/aitbc_cli/commands/analytics.py +++ b/cli/commands/analytics.py @@ -4,9 +4,9 @@ import click import asyncio from datetime import datetime, timedelta from typing import Optional -from ..core.config import load_multichain_config -from ..core.analytics import ChainAnalytics -from ..utils import output, error, success +from core.config import load_multichain_config +from core.analytics import ChainAnalytics +from utils import output, error, success @click.group() def analytics(): diff --git a/cli/aitbc_cli/commands/auth.py b/cli/commands/auth.py similarity index 98% rename from cli/aitbc_cli/commands/auth.py rename to cli/commands/auth.py index eea4e0f2..49e613c2 100755 --- a/cli/aitbc_cli/commands/auth.py +++ b/cli/commands/auth.py @@ -3,8 +3,8 @@ import click import os from typing import Optional -from ..auth import AuthManager -from ..utils import output, success, error, warning +from auth import AuthManager +from utils import output, success, error, warning @click.group() diff --git a/cli/aitbc_cli/commands/blockchain.py b/cli/commands/blockchain.py similarity index 99% rename from cli/aitbc_cli/commands/blockchain.py rename to cli/commands/blockchain.py index 2074aebe..1b028a92 100755 --- a/cli/aitbc_cli/commands/blockchain.py +++ b/cli/commands/blockchain.py @@ -12,7 +12,7 @@ def _get_node_endpoint(ctx): return "http://127.0.0.1:8006" # Use new blockchain RPC port from typing import Optional, List -from ..utils import output, error +from utils import output, error import os @@ -1016,7 +1016,7 @@ def verify_genesis(ctx, chain: str, genesis_hash: Optional[str], verify_signatur """Verify genesis block integrity for a specific chain""" try: import httpx - from ..utils import success + from utils import success with httpx.Client() as client: # Get genesis block for the specified chain @@ -1129,7 +1129,7 @@ def genesis_hash(ctx, chain: str): """Get the genesis block hash for a specific chain""" try: import httpx - from ..utils import success + from utils import success with httpx.Client() as client: response = client.get( diff --git a/cli/aitbc_cli/commands/chain.py b/cli/commands/chain.py similarity index 98% rename from cli/aitbc_cli/commands/chain.py rename to cli/commands/chain.py index 1c3c7a60..b4bf82ea 100755 --- a/cli/aitbc_cli/commands/chain.py +++ b/cli/commands/chain.py @@ -2,10 +2,10 @@ import click from typing import Optional -from ..core.chain_manager import ChainManager, ChainNotFoundError, NodeNotAvailableError -from ..core.config import MultiChainConfig, load_multichain_config -from ..models.chain import ChainType -from ..utils import output, error, success +from core.chain_manager import ChainManager, ChainNotFoundError, NodeNotAvailableError +from core.config import MultiChainConfig, load_multichain_config +from models.chain import ChainType +from utils import output, error, success @click.group() def chain(): @@ -200,7 +200,7 @@ def create(ctx, config_file, node, dry_run): """Create a new chain from configuration file""" try: import yaml - from ..models.chain import ChainConfig + from models.chain import ChainConfig config = load_multichain_config() chain_manager = ChainManager(config) diff --git a/cli/aitbc_cli/commands/client.py b/cli/commands/client.py similarity index 99% rename from cli/aitbc_cli/commands/client.py rename to cli/commands/client.py index 9520641f..27fe2434 100755 --- a/cli/aitbc_cli/commands/client.py +++ b/cli/commands/client.py @@ -5,7 +5,7 @@ import httpx import json import time from typing import Optional -from ..utils import output, error, success +from utils import output, error, success @click.group() @@ -348,7 +348,7 @@ def batch_submit(ctx, file_path: str, file_format: Optional[str], retries: int, """Submit multiple jobs from a CSV or JSON file""" import csv from pathlib import Path - from ..utils import progress_bar + from utils import progress_bar config = ctx.obj['config'] path = Path(file_path) diff --git a/cli/aitbc_cli/commands/compliance.py b/cli/commands/compliance.py similarity index 99% rename from cli/aitbc_cli/commands/compliance.py rename to cli/commands/compliance.py index d71555a6..d78cc45e 100755 --- a/cli/aitbc_cli/commands/compliance.py +++ b/cli/commands/compliance.py @@ -11,7 +11,7 @@ from typing import Optional, Dict, Any from datetime import datetime # Import compliance providers -from aitbc_cli.kyc_aml_providers import submit_kyc_verification, check_kyc_status, perform_aml_screening +from kyc_aml_providers import submit_kyc_verification, check_kyc_status, perform_aml_screening @click.group() def compliance(): diff --git a/cli/aitbc_cli/commands/config.py b/cli/commands/config.py similarity index 98% rename from cli/aitbc_cli/commands/config.py rename to cli/commands/config.py index 17087732..76497443 100755 --- a/cli/aitbc_cli/commands/config.py +++ b/cli/commands/config.py @@ -8,8 +8,8 @@ import yaml import json from pathlib import Path from typing import Optional, Dict, Any -from ..config import get_config, Config -from ..utils import output, error, success +from config import get_config, Config +from utils import output, error, success @click.group() @@ -459,7 +459,7 @@ def delete(ctx, name: str): @click.pass_context def set_secret(ctx, key: str, value: str): """Set an encrypted configuration value""" - from ..utils import encrypt_value + from utils import encrypt_value config_dir = Path.home() / ".config" / "aitbc" config_dir.mkdir(parents=True, exist_ok=True) @@ -488,7 +488,7 @@ def set_secret(ctx, key: str, value: str): @click.pass_context def get_secret(ctx, key: str): """Get a decrypted configuration value""" - from ..utils import decrypt_value + from utils import decrypt_value secrets_file = Path.home() / ".config" / "aitbc" / "secrets.json" diff --git a/cli/aitbc_cli/commands/cross_chain.py b/cli/commands/cross_chain.py similarity index 99% rename from cli/aitbc_cli/commands/cross_chain.py rename to cli/commands/cross_chain.py index 7eba4916..888c0219 100755 --- a/cli/aitbc_cli/commands/cross_chain.py +++ b/cli/commands/cross_chain.py @@ -5,8 +5,8 @@ import httpx import json from typing import Optional from tabulate import tabulate -from ..config import get_config -from ..utils import success, error, output +from config import get_config +from utils import success, error, output @click.group() diff --git a/cli/aitbc_cli/commands/dao.py b/cli/commands/dao.py similarity index 99% rename from cli/aitbc_cli/commands/dao.py rename to cli/commands/dao.py index cc0a1325..237286b0 100644 --- a/cli/aitbc_cli/commands/dao.py +++ b/cli/commands/dao.py @@ -9,8 +9,8 @@ import json from datetime import datetime, timedelta from typing import List, Dict, Any from web3 import Web3 -from ..utils.blockchain import get_web3_connection, get_contract -from ..utils.config import load_config +from utils.blockchain import get_web3_connection, get_contract +from utils.config import load_config @click.group() def dao(): diff --git a/cli/commands/deployment.py b/cli/commands/deployment.py new file mode 100644 index 00000000..0d3c4570 --- /dev/null +++ b/cli/commands/deployment.py @@ -0,0 +1,91 @@ +"""Production deployment guidance for AITBC CLI""" + +import click +from utils import output, error, success + +@click.group() +def deploy(): + """Production deployment guidance and setup""" + pass + +@deploy.command() +@click.option('--service', default='all', help='Service to deploy (all, coordinator, blockchain, marketplace)') +@click.option('--environment', default='production', help='Deployment environment') +def setup(service, environment): + """Get deployment setup instructions""" + output(f"🚀 {environment.title()} Deployment Setup for {service.title()}", None) + + instructions = { + 'coordinator': [ + "1. Install dependencies: pip install -r requirements.txt", + "2. Set environment variables in .env file", + "3. Run: python -m coordinator.main", + "4. Configure nginx reverse proxy", + "5. Set up SSL certificates" + ], + 'blockchain': [ + "1. Install blockchain node dependencies", + "2. Initialize genesis block: aitbc genesis init", + "3. Start node: python -m blockchain.node", + "4. Configure peer connections", + "5. Enable mining if needed" + ], + 'marketplace': [ + "1. Install marketplace dependencies", + "2. Set up database: postgresql-setup.sh", + "3. Run migrations: python -m marketplace.migrate", + "4. Start service: python -m marketplace.main", + "5. Configure GPU mining nodes" + ], + 'all': [ + "📋 Complete AITBC Platform Deployment:", + "", + "1. Prerequisites:", + " - Python 3.13+", + " - PostgreSQL 14+", + " - Redis 6+", + " - Docker (optional)", + "", + "2. Environment Setup:", + " - Copy .env.example to .env", + " - Configure database URLs", + " - Set API keys and secrets", + "", + "3. Database Setup:", + " - createdb aitbc", + " - Run migrations: python manage.py migrate", + "", + "4. Service Deployment:", + " - Coordinator: python -m coordinator.main", + " - Blockchain: python -m blockchain.node", + " - Marketplace: python -m marketplace.main", + "", + "5. Frontend Setup:", + " - npm install", + " - npm run build", + " - Configure web server" + ] + } + + for step in instructions.get(service, instructions['all']): + output(step, None) + + output(f"\n💡 For detailed deployment guides, see: docs/deployment/{environment}.md", None) + +@deploy.command() +@click.option('--service', help='Service to check') +def status(service): + """Check deployment status""" + output(f"📊 Deployment Status Check for {service or 'All Services'}", None) + + checks = [ + "Coordinator API: http://localhost:8000/health", + "Blockchain Node: http://localhost:8006/status", + "Marketplace: http://localhost:8014/health", + "Wallet Service: http://localhost:8002/status" + ] + + for check in checks: + output(f" • {check}", None) + + output("\n💡 Use curl or browser to check each endpoint", None) diff --git a/cli/aitbc_cli/commands/enterprise_integration.py b/cli/commands/enterprise_integration.py similarity index 99% rename from cli/aitbc_cli/commands/enterprise_integration.py rename to cli/commands/enterprise_integration.py index 1e17a7b4..2f80527c 100755 --- a/cli/aitbc_cli/commands/enterprise_integration.py +++ b/cli/commands/enterprise_integration.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/exchange.py b/cli/commands/exchange.py similarity index 99% rename from cli/aitbc_cli/commands/exchange.py rename to cli/commands/exchange.py index 3d822185..e7cfb699 100755 --- a/cli/aitbc_cli/commands/exchange.py +++ b/cli/commands/exchange.py @@ -7,8 +7,8 @@ import os from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime -from ..utils import output, error, success, warning -from ..config import get_config +from utils import output, error, success, warning +from config import get_config @click.group() diff --git a/cli/aitbc_cli/commands/explorer.py b/cli/commands/explorer.py similarity index 99% rename from cli/aitbc_cli/commands/explorer.py rename to cli/commands/explorer.py index 65808e6e..41837d52 100755 --- a/cli/aitbc_cli/commands/explorer.py +++ b/cli/commands/explorer.py @@ -4,7 +4,7 @@ import click import subprocess import json from typing import Optional, List -from ..utils import output, error +from utils import output, error def _get_explorer_endpoint(ctx): diff --git a/cli/aitbc_cli/commands/genesis.py b/cli/commands/genesis.py similarity index 98% rename from cli/aitbc_cli/commands/genesis.py rename to cli/commands/genesis.py index 9cabfabd..0d4ef374 100755 --- a/cli/aitbc_cli/commands/genesis.py +++ b/cli/commands/genesis.py @@ -5,11 +5,11 @@ import json import yaml from pathlib import Path from datetime import datetime -from ..core.genesis_generator import GenesisGenerator, GenesisValidationError -from ..core.config import MultiChainConfig, load_multichain_config -from ..models.chain import GenesisConfig -from ..utils import output, error, success -from .keystore import create_keystore_via_script +from core.genesis_generator import GenesisGenerator, GenesisValidationError +from core.config import MultiChainConfig, load_multichain_config +from models.chain import GenesisConfig +from utils import output, error, success +from commands.keystore import create_keystore_via_script import subprocess import sys @@ -141,7 +141,7 @@ def validate(ctx, genesis_file): with open(genesis_path, 'r') as f: genesis_data = json.load(f) - from ..models.chain import GenesisBlock + from models.chain import GenesisBlock genesis_block = GenesisBlock(**genesis_data) # Validate genesis block diff --git a/cli/aitbc_cli/commands/genesis_protection.py b/cli/commands/genesis_protection.py similarity index 99% rename from cli/aitbc_cli/commands/genesis_protection.py rename to cli/commands/genesis_protection.py index 020af231..896c4133 100755 --- a/cli/aitbc_cli/commands/genesis_protection.py +++ b/cli/commands/genesis_protection.py @@ -6,7 +6,7 @@ import hashlib from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/global_ai_agents.py b/cli/commands/global_ai_agents.py similarity index 98% rename from cli/aitbc_cli/commands/global_ai_agents.py rename to cli/commands/global_ai_agents.py index 39c1cf8f..0a8ee6a2 100755 --- a/cli/aitbc_cli/commands/global_ai_agents.py +++ b/cli/commands/global_ai_agents.py @@ -59,7 +59,7 @@ def status(agent_id, test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/global_infrastructure.py b/cli/commands/global_infrastructure.py similarity index 99% rename from cli/aitbc_cli/commands/global_infrastructure.py rename to cli/commands/global_infrastructure.py index 2653fad4..dc3c5e9e 100755 --- a/cli/aitbc_cli/commands/global_infrastructure.py +++ b/cli/commands/global_infrastructure.py @@ -557,7 +557,7 @@ def deployment_status(deployment_id, test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/governance.py b/cli/commands/governance.py similarity index 99% rename from cli/aitbc_cli/commands/governance.py rename to cli/commands/governance.py index 8ee9d01c..9214757b 100755 --- a/cli/aitbc_cli/commands/governance.py +++ b/cli/commands/governance.py @@ -8,7 +8,7 @@ import time from pathlib import Path from typing import Optional from datetime import datetime, timedelta -from ..utils import output, error, success +from utils import output, error, success GOVERNANCE_DIR = Path.home() / ".aitbc" / "governance" diff --git a/cli/aitbc_cli/commands/keystore.py b/cli/commands/keystore.py similarity index 100% rename from cli/aitbc_cli/commands/keystore.py rename to cli/commands/keystore.py diff --git a/cli/aitbc_cli/commands/market_maker.py b/cli/commands/market_maker.py similarity index 99% rename from cli/aitbc_cli/commands/market_maker.py rename to cli/commands/market_maker.py index 91457179..d30b21b7 100755 --- a/cli/aitbc_cli/commands/market_maker.py +++ b/cli/commands/market_maker.py @@ -7,7 +7,7 @@ import httpx from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime, timedelta -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/marketplace.py b/cli/commands/marketplace.py similarity index 99% rename from cli/aitbc_cli/commands/marketplace.py rename to cli/commands/marketplace.py index a9bddb68..a2e8a8f8 100755 --- a/cli/aitbc_cli/commands/marketplace.py +++ b/cli/commands/marketplace.py @@ -5,7 +5,7 @@ import httpx import json import asyncio from typing import Optional, List, Dict, Any -from ..utils import output, error, success +from utils import output, error, success import os diff --git a/cli/aitbc_cli/commands/marketplace_advanced.py b/cli/commands/marketplace_advanced.py similarity index 99% rename from cli/aitbc_cli/commands/marketplace_advanced.py rename to cli/commands/marketplace_advanced.py index 32cecb5f..64451262 100755 --- a/cli/aitbc_cli/commands/marketplace_advanced.py +++ b/cli/commands/marketplace_advanced.py @@ -6,7 +6,7 @@ import json import base64 from typing import Optional, Dict, Any, List from pathlib import Path -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/marketplace_cmd.py b/cli/commands/marketplace_cmd.py similarity index 99% rename from cli/aitbc_cli/commands/marketplace_cmd.py rename to cli/commands/marketplace_cmd.py index e3f25266..055faa5f 100755 --- a/cli/aitbc_cli/commands/marketplace_cmd.py +++ b/cli/commands/marketplace_cmd.py @@ -6,12 +6,12 @@ import json from decimal import Decimal from datetime import datetime from typing import Optional -from ..core.config import load_multichain_config -from ..core.marketplace import ( +from core.config import load_multichain_config +from core.marketplace import ( GlobalChainMarketplace, ChainType, MarketplaceStatus, TransactionStatus ) -from ..utils import output, error, success +from utils import output, error, success @click.group() def marketplace(): diff --git a/cli/aitbc_cli/commands/miner.py b/cli/commands/miner.py similarity index 99% rename from cli/aitbc_cli/commands/miner.py rename to cli/commands/miner.py index 21037f6b..3eb49ad9 100755 --- a/cli/aitbc_cli/commands/miner.py +++ b/cli/commands/miner.py @@ -6,7 +6,7 @@ import json import time import concurrent.futures from typing import Optional, Dict, Any, List -from ..utils import output, error, success +from utils import output, error, success @click.group(invoke_without_command=True) diff --git a/cli/aitbc_cli/commands/monitor.py b/cli/commands/monitor.py similarity index 99% rename from cli/aitbc_cli/commands/monitor.py rename to cli/commands/monitor.py index 79972f9a..56d360c5 100755 --- a/cli/aitbc_cli/commands/monitor.py +++ b/cli/commands/monitor.py @@ -7,7 +7,7 @@ import time from pathlib import Path from typing import Optional from datetime import datetime, timedelta -from ..utils import output, error, success, console +from utils import output, error, success, console @click.group() diff --git a/cli/aitbc_cli/commands/multi_region_load_balancer.py b/cli/commands/multi_region_load_balancer.py similarity index 98% rename from cli/aitbc_cli/commands/multi_region_load_balancer.py rename to cli/commands/multi_region_load_balancer.py index 38015796..d3f29c4b 100755 --- a/cli/aitbc_cli/commands/multi_region_load_balancer.py +++ b/cli/commands/multi_region_load_balancer.py @@ -53,7 +53,7 @@ def status(test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/multimodal.py b/cli/commands/multimodal.py similarity index 99% rename from cli/aitbc_cli/commands/multimodal.py rename to cli/commands/multimodal.py index d48677ac..d970c51a 100755 --- a/cli/aitbc_cli/commands/multimodal.py +++ b/cli/commands/multimodal.py @@ -7,7 +7,7 @@ import base64 import mimetypes from typing import Optional, Dict, Any, List from pathlib import Path -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/multisig.py b/cli/commands/multisig.py similarity index 99% rename from cli/aitbc_cli/commands/multisig.py rename to cli/commands/multisig.py index 70432503..0876d5e9 100755 --- a/cli/aitbc_cli/commands/multisig.py +++ b/cli/commands/multisig.py @@ -7,7 +7,7 @@ import uuid from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime, timedelta -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/node.py b/cli/commands/node.py similarity index 97% rename from cli/aitbc_cli/commands/node.py rename to cli/commands/node.py index d1f7de99..937f2249 100755 --- a/cli/aitbc_cli/commands/node.py +++ b/cli/commands/node.py @@ -2,9 +2,9 @@ import click from typing import Optional -from ..core.config import MultiChainConfig, load_multichain_config, get_default_node_config, add_node_config, remove_node_config -from ..core.node_client import NodeClient -from ..utils import output, error, success +from core.config import MultiChainConfig, load_multichain_config, get_default_node_config, add_node_config, remove_node_config +from core.node_client import NodeClient +from utils import output, error, success @click.group() def node(): @@ -192,7 +192,7 @@ def add(ctx, node_id, endpoint, timeout, max_connections, retry_count): config = add_node_config(config, node_config) - from ..core.config import save_multichain_config + from core.config import save_multichain_config save_multichain_config(config) success(f"Node {node_id} added successfully!") @@ -241,7 +241,7 @@ def remove(ctx, node_id, force): config = remove_node_config(config, node_id) - from ..core.config import save_multichain_config + from core.config import save_multichain_config save_multichain_config(config) success(f"Node {node_id} removed successfully!") diff --git a/cli/aitbc_cli/commands/openclaw.py b/cli/commands/openclaw.py similarity index 99% rename from cli/aitbc_cli/commands/openclaw.py rename to cli/commands/openclaw.py index fad1f85a..3de5ab22 100755 --- a/cli/aitbc_cli/commands/openclaw.py +++ b/cli/commands/openclaw.py @@ -5,7 +5,7 @@ import httpx import json import time from typing import Optional, Dict, Any, List -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/optimize.py b/cli/commands/optimize.py similarity index 99% rename from cli/aitbc_cli/commands/optimize.py rename to cli/commands/optimize.py index 7f44c1c5..a6ac1faa 100755 --- a/cli/aitbc_cli/commands/optimize.py +++ b/cli/commands/optimize.py @@ -5,7 +5,7 @@ import httpx import json import time from typing import Optional, Dict, Any, List -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/oracle.py b/cli/commands/oracle.py similarity index 99% rename from cli/aitbc_cli/commands/oracle.py rename to cli/commands/oracle.py index 543b97c1..effb9c64 100755 --- a/cli/aitbc_cli/commands/oracle.py +++ b/cli/commands/oracle.py @@ -5,7 +5,7 @@ import json from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime, timedelta -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/performance_test.py b/cli/commands/performance_test.py similarity index 100% rename from cli/aitbc_cli/commands/performance_test.py rename to cli/commands/performance_test.py diff --git a/cli/aitbc_cli/commands/plugin_analytics.py b/cli/commands/plugin_analytics.py similarity index 98% rename from cli/aitbc_cli/commands/plugin_analytics.py rename to cli/commands/plugin_analytics.py index 5bb269d2..0bd3920e 100755 --- a/cli/aitbc_cli/commands/plugin_analytics.py +++ b/cli/commands/plugin_analytics.py @@ -59,7 +59,7 @@ def dashboard(plugin_id, days, test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/plugin_marketplace.py b/cli/commands/plugin_marketplace.py similarity index 99% rename from cli/aitbc_cli/commands/plugin_marketplace.py rename to cli/commands/plugin_marketplace.py index d8c082df..683d4062 100755 --- a/cli/aitbc_cli/commands/plugin_marketplace.py +++ b/cli/commands/plugin_marketplace.py @@ -565,7 +565,7 @@ def download(plugin_id, license_key, test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/plugin_registry.py b/cli/commands/plugin_registry.py similarity index 99% rename from cli/aitbc_cli/commands/plugin_registry.py rename to cli/commands/plugin_registry.py index 6250837a..825f73a8 100755 --- a/cli/aitbc_cli/commands/plugin_registry.py +++ b/cli/commands/plugin_registry.py @@ -489,7 +489,7 @@ def status(test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/plugin_security.py b/cli/commands/plugin_security.py similarity index 98% rename from cli/aitbc_cli/commands/plugin_security.py rename to cli/commands/plugin_security.py index 43cb5e5d..b360b0a3 100755 --- a/cli/aitbc_cli/commands/plugin_security.py +++ b/cli/commands/plugin_security.py @@ -85,7 +85,7 @@ def status(test_mode): def get_config(): """Get CLI configuration""" try: - from .config import get_config + from config import get_config return get_config() except ImportError: # Fallback for testing diff --git a/cli/aitbc_cli/commands/production_deploy.py b/cli/commands/production_deploy.py similarity index 100% rename from cli/aitbc_cli/commands/production_deploy.py rename to cli/commands/production_deploy.py diff --git a/cli/aitbc_cli/commands/regulatory.py b/cli/commands/regulatory.py similarity index 99% rename from cli/aitbc_cli/commands/regulatory.py rename to cli/commands/regulatory.py index 223f54c2..e18740ac 100755 --- a/cli/aitbc_cli/commands/regulatory.py +++ b/cli/commands/regulatory.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/security_test.py b/cli/commands/security_test.py similarity index 100% rename from cli/aitbc_cli/commands/security_test.py rename to cli/commands/security_test.py diff --git a/cli/aitbc_cli/commands/simulate.py b/cli/commands/simulate.py similarity index 99% rename from cli/aitbc_cli/commands/simulate.py rename to cli/commands/simulate.py index 1d8266c0..2981d940 100755 --- a/cli/aitbc_cli/commands/simulate.py +++ b/cli/commands/simulate.py @@ -6,7 +6,7 @@ import time import random from pathlib import Path from typing import Optional, List, Dict, Any -from ..utils import output, error, success +from utils import output, error, success @click.group() diff --git a/cli/aitbc_cli/commands/surveillance.py b/cli/commands/surveillance.py similarity index 99% rename from cli/aitbc_cli/commands/surveillance.py rename to cli/commands/surveillance.py index 8817a3a6..02b86c8d 100755 --- a/cli/aitbc_cli/commands/surveillance.py +++ b/cli/commands/surveillance.py @@ -9,7 +9,7 @@ import asyncio import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -from aitbc_cli.imports import ensure_coordinator_api_imports +from imports import ensure_coordinator_api_imports ensure_coordinator_api_imports() diff --git a/cli/aitbc_cli/commands/swarm.py b/cli/commands/swarm.py similarity index 99% rename from cli/aitbc_cli/commands/swarm.py rename to cli/commands/swarm.py index 4197c902..b315c9f4 100755 --- a/cli/aitbc_cli/commands/swarm.py +++ b/cli/commands/swarm.py @@ -4,7 +4,7 @@ import click import httpx import json from typing import Optional, Dict, Any, List -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/sync.py b/cli/commands/sync.py similarity index 97% rename from cli/aitbc_cli/commands/sync.py rename to cli/commands/sync.py index c88e5022..8af7c822 100644 --- a/cli/aitbc_cli/commands/sync.py +++ b/cli/commands/sync.py @@ -6,7 +6,7 @@ from pathlib import Path import click -from ..utils import success, error, run_subprocess +from utils import success, error, run_subprocess @click.group() diff --git a/cli/aitbc_cli/commands/test_cli.py b/cli/commands/test_cli.py similarity index 98% rename from cli/aitbc_cli/commands/test_cli.py rename to cli/commands/test_cli.py index 14a03e29..8a8536a4 100755 --- a/cli/aitbc_cli/commands/test_cli.py +++ b/cli/commands/test_cli.py @@ -11,8 +11,8 @@ from pathlib import Path from typing import Dict, Any, Optional from unittest.mock import Mock, patch -from ..utils import output, success, error, warning -from ..config import get_config +from utils import output, success, error, warning +from config import get_config @click.group() @@ -118,7 +118,7 @@ def api(ctx, endpoint, method, data): @click.pass_context def wallet(ctx, wallet_name, test_operations): """Test wallet functionality""" - from ..commands.wallet import wallet as wallet_cmd + from commands.wallet import wallet as wallet_cmd output(f"Testing wallet functionality with wallet: {wallet_name}") @@ -164,7 +164,7 @@ def wallet(ctx, wallet_name, test_operations): @click.pass_context def job(ctx, job_type, test_data): """Test job submission and management""" - from ..commands.client import client as client_cmd + from commands.client import client as client_cmd output(f"Testing job submission with type: {job_type}") @@ -220,7 +220,7 @@ def job(ctx, job_type, test_data): @click.pass_context def marketplace(ctx, gpu_type, price): """Test marketplace functionality""" - from ..commands.marketplace import marketplace as marketplace_cmd + from commands.marketplace import marketplace as marketplace_cmd output(f"Testing marketplace functionality for {gpu_type} at {price} AITBC/hour") @@ -252,7 +252,7 @@ def marketplace(ctx, gpu_type, price): @click.pass_context def blockchain(ctx, test_endpoints): """Test blockchain functionality""" - from ..commands.blockchain import blockchain as blockchain_cmd + from commands.blockchain import blockchain as blockchain_cmd output("Testing blockchain functionality") diff --git a/cli/aitbc_cli/commands/transfer_control.py b/cli/commands/transfer_control.py similarity index 99% rename from cli/aitbc_cli/commands/transfer_control.py rename to cli/commands/transfer_control.py index 7169801d..c2b5d69d 100755 --- a/cli/aitbc_cli/commands/transfer_control.py +++ b/cli/commands/transfer_control.py @@ -5,7 +5,7 @@ import json from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime, timedelta -from ..utils import output, error, success, warning +from utils import output, error, success, warning @click.group() diff --git a/cli/aitbc_cli/commands/wallet.py b/cli/commands/wallet.py similarity index 98% rename from cli/aitbc_cli/commands/wallet.py rename to cli/commands/wallet.py index 6c30532d..d79ebb13 100755 --- a/cli/aitbc_cli/commands/wallet.py +++ b/cli/commands/wallet.py @@ -9,7 +9,7 @@ import yaml from pathlib import Path from typing import Optional, Dict, Any, List from datetime import datetime, timedelta -from ..utils import output, error, success, encrypt_value, decrypt_value +from utils import output, error, success, encrypt_value, decrypt_value import getpass @@ -124,8 +124,8 @@ def wallet(ctx, wallet_name: Optional[str], wallet_path: Optional[str], use_daem ctx.obj["use_daemon"] = use_daemon # Initialize dual-mode adapter - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=use_daemon) @@ -188,8 +188,8 @@ def create(ctx, name: str, wallet_type: str, no_encrypt: bool): if use_daemon and not adapter.is_daemon_available(): error("Wallet daemon is not available. Falling back to file-based wallet.") # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=False) ctx.obj["wallet_adapter"] = adapter @@ -254,8 +254,8 @@ def list(ctx): if use_daemon and not adapter.is_daemon_available(): error("Wallet daemon is not available. Falling back to file-based wallet listing.") # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=False) @@ -306,8 +306,8 @@ def switch(ctx, name: str): if use_daemon and not adapter.is_daemon_available(): error("Wallet daemon is not available. Falling back to file-based wallet switching.") # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=False) @@ -846,8 +846,8 @@ def send(ctx, to_address: str, amount: float, description: Optional[str]): if use_daemon and not adapter.is_daemon_available(): error("Wallet daemon is not available. Falling back to file-based wallet send.") # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=False) ctx.obj["wallet_adapter"] = adapter @@ -882,8 +882,8 @@ def balance(ctx): if use_daemon and not adapter.is_daemon_available(): error("Wallet daemon is not available. Falling back to file-based wallet balance.") # Switch to file mode - from ..config import get_config - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from config import get_config + from dual_mode_wallet_adapter import DualModeWalletAdapter config = get_config() adapter = DualModeWalletAdapter(config, use_daemon=False) ctx.obj["wallet_adapter"] = adapter @@ -919,8 +919,8 @@ def daemon(): @click.pass_context def status(ctx): """Check wallet daemon status""" - from ..config import get_config - from ..wallet_daemon_client import WalletDaemonClient + from config import get_config + from wallet_daemon_client import WalletDaemonClient config = get_config() client = WalletDaemonClient(config) @@ -942,7 +942,7 @@ def status(ctx): @click.pass_context def configure(ctx): """Configure wallet daemon settings""" - from ..config import get_config + from config import get_config config = get_config() @@ -961,8 +961,8 @@ def configure(ctx): @click.pass_context def migrate_to_daemon(ctx, wallet_name: str, password: Optional[str], new_password: Optional[str], force: bool): """Migrate a file-based wallet to daemon storage""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config + from wallet_migration_service import WalletMigrationService + from config import get_config config = get_config() migration_service = WalletMigrationService(config) @@ -988,8 +988,8 @@ def migrate_to_daemon(ctx, wallet_name: str, password: Optional[str], new_passwo @click.pass_context def migrate_to_file(ctx, wallet_name: str, password: Optional[str], new_password: Optional[str], force: bool): """Migrate a daemon-based wallet to file storage""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config + from wallet_migration_service import WalletMigrationService + from config import get_config config = get_config() migration_service = WalletMigrationService(config) @@ -1011,8 +1011,8 @@ def migrate_to_file(ctx, wallet_name: str, password: Optional[str], new_password @click.pass_context def migration_status(ctx): """Show wallet migration status""" - from ..wallet_migration_service import WalletMigrationService - from ..config import get_config + from wallet_migration_service import WalletMigrationService + from config import get_config config = get_config() migration_service = WalletMigrationService(config) @@ -1452,7 +1452,7 @@ def multisig_challenge(ctx, wallet_name: str, tx_id: str): return # Import crypto utilities - from ..utils.crypto_utils import multisig_security + from utils.crypto_utils import multisig_security try: # Create signing request @@ -1481,7 +1481,7 @@ def multisig_challenge(ctx, wallet_name: str, tx_id: str): @click.pass_context def sign_challenge(ctx, challenge: str, private_key: str): """Sign a cryptographic challenge (for testing multisig)""" - from ..utils.crypto_utils import sign_challenge + from utils.crypto_utils import sign_challenge try: signature = sign_challenge(challenge, private_key) @@ -1520,7 +1520,7 @@ def multisig_sign(ctx, wallet_name: str, tx_id: str, signer: str, signature: str return # Import crypto utilities - from ..utils.crypto_utils import multisig_security + from utils.crypto_utils import multisig_security # Verify signature cryptographically success, message = multisig_security.verify_and_add_signature(tx_id, signature, signer) @@ -2112,7 +2112,7 @@ def multisig_create(ctx, threshold: int, signers: tuple, wallet_name: Optional[s try: if ctx.obj.get("use_daemon"): # Use wallet daemon for multi-sig creation - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from dual_mode_wallet_adapter import DualModeWalletAdapter adapter = DualModeWalletAdapter(config) result = adapter.create_multisig_wallet( @@ -2170,7 +2170,7 @@ def set_limit(ctx, amount: float, period: str, wallet_name: Optional[str]): try: if ctx.obj.get("use_daemon"): # Use wallet daemon - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from dual_mode_wallet_adapter import DualModeWalletAdapter adapter = DualModeWalletAdapter(config) result = adapter.set_transfer_limit( @@ -2232,7 +2232,7 @@ def time_lock(ctx, amount: float, duration: int, recipient: str, wallet_name: Op try: if ctx.obj.get("use_daemon"): # Use wallet daemon - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from dual_mode_wallet_adapter import DualModeWalletAdapter adapter = DualModeWalletAdapter(config) result = adapter.create_time_lock( @@ -2352,7 +2352,7 @@ def audit_trail(ctx, wallet_name: Optional[str], days: int): try: if ctx.obj.get("use_daemon"): # Use wallet daemon for audit - from ..dual_mode_wallet_adapter import DualModeWalletAdapter + from dual_mode_wallet_adapter import DualModeWalletAdapter adapter = DualModeWalletAdapter(config) result = adapter.get_audit_trail( diff --git a/cli/aitbc_cli/config/__init__.py b/cli/config/__init__.py similarity index 100% rename from cli/aitbc_cli/config/__init__.py rename to cli/config/__init__.py diff --git a/cli/aitbc_cli/core/__init__.py b/cli/core/__init__.py similarity index 100% rename from cli/aitbc_cli/core/__init__.py rename to cli/core/__init__.py diff --git a/cli/aitbc_cli/core/agent_communication.py b/cli/core/agent_communication.py similarity index 99% rename from cli/aitbc_cli/core/agent_communication.py rename to cli/core/agent_communication.py index b40b2ede..6b6866a1 100755 --- a/cli/aitbc_cli/core/agent_communication.py +++ b/cli/core/agent_communication.py @@ -13,8 +13,8 @@ from enum import Enum import uuid from collections import defaultdict -from ..core.config import MultiChainConfig -from ..core.node_client import NodeClient +from core.config import MultiChainConfig +from core.node_client import NodeClient class MessageType(Enum): """Agent message types""" diff --git a/cli/aitbc_cli/core/analytics.py b/cli/core/analytics.py similarity index 99% rename from cli/aitbc_cli/core/analytics.py rename to cli/core/analytics.py index f3d8467f..df5df5e1 100755 --- a/cli/aitbc_cli/core/analytics.py +++ b/cli/core/analytics.py @@ -11,9 +11,9 @@ from dataclasses import dataclass, asdict from collections import defaultdict, deque import statistics -from ..core.config import MultiChainConfig -from ..core.node_client import NodeClient -from ..models.chain import ChainInfo, ChainType, ChainStatus +from core.config import MultiChainConfig +from core.node_client import NodeClient +from models.chain import ChainInfo, ChainType, ChainStatus @dataclass class ChainMetrics: diff --git a/cli/aitbc_cli/core/chain_manager.py b/cli/core/chain_manager.py similarity index 99% rename from cli/aitbc_cli/core/chain_manager.py rename to cli/core/chain_manager.py index 1855f8d9..23c6b338 100755 --- a/cli/aitbc_cli/core/chain_manager.py +++ b/cli/core/chain_manager.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Dict, List, Optional, Any from .config import MultiChainConfig, get_node_config from .node_client import NodeClient -from ..models.chain import ( +from models.chain import ( ChainConfig, ChainInfo, ChainType, ChainStatus, GenesisBlock, ChainMigrationPlan, ChainMigrationResult, ChainBackupResult, ChainRestoreResult diff --git a/cli/aitbc_cli/core/config.py b/cli/core/config.py similarity index 100% rename from cli/aitbc_cli/core/config.py rename to cli/core/config.py diff --git a/cli/aitbc_cli/core/genesis_generator.py b/cli/core/genesis_generator.py similarity index 99% rename from cli/aitbc_cli/core/genesis_generator.py rename to cli/core/genesis_generator.py index 46e27961..22e2f031 100755 --- a/cli/aitbc_cli/core/genesis_generator.py +++ b/cli/core/genesis_generator.py @@ -8,8 +8,8 @@ import yaml from datetime import datetime from pathlib import Path from typing import Dict, Any, Optional -from ..core.config import MultiChainConfig -from ..models.chain import GenesisBlock, GenesisConfig, ChainType, ConsensusAlgorithm +from core.config import MultiChainConfig +from models.chain import GenesisBlock, GenesisConfig, ChainType, ConsensusAlgorithm class GenesisValidationError(Exception): """Genesis validation error""" diff --git a/cli/aitbc_cli/core/marketplace.py b/cli/core/marketplace.py similarity index 99% rename from cli/aitbc_cli/core/marketplace.py rename to cli/core/marketplace.py index 0760e180..c0b5b897 100755 --- a/cli/aitbc_cli/core/marketplace.py +++ b/cli/core/marketplace.py @@ -14,8 +14,8 @@ import uuid from decimal import Decimal from collections import defaultdict -from ..core.config import MultiChainConfig -from ..core.node_client import NodeClient +from core.config import MultiChainConfig +from core.node_client import NodeClient class ChainType(Enum): """Chain types in marketplace""" diff --git a/cli/aitbc_cli/core/node_client.py b/cli/core/node_client.py similarity index 98% rename from cli/aitbc_cli/core/node_client.py rename to cli/core/node_client.py index 830db9ef..1e799d4e 100755 --- a/cli/aitbc_cli/core/node_client.py +++ b/cli/core/node_client.py @@ -6,8 +6,8 @@ import asyncio import httpx import json from typing import Dict, List, Optional, Any -from ..core.config import NodeConfig -from ..models.chain import ChainInfo, ChainType, ChainStatus, ConsensusAlgorithm +from core.config import NodeConfig +from models.chain import ChainInfo, ChainType, ChainStatus, ConsensusAlgorithm class NodeClient: """Client for communicating with AITBC nodes""" @@ -244,7 +244,7 @@ class NodeClient: def _parse_chain_info(self, chain_data: Dict[str, Any]) -> ChainInfo: """Parse chain data from node response""" from datetime import datetime - from ..models.chain import PrivacyConfig + from models.chain import PrivacyConfig return ChainInfo( id=chain_data.get("chain_id", chain_data.get("id", "unknown")), @@ -297,7 +297,7 @@ class NodeClient: def _get_mock_chains(self) -> List[ChainInfo]: """Get mock chains for development""" from datetime import datetime - from ..models.chain import PrivacyConfig + from models.chain import PrivacyConfig return [ ChainInfo( diff --git a/cli/aitbc_cli/dual_mode_wallet_adapter.py b/cli/dual_mode_wallet_adapter.py similarity index 100% rename from cli/aitbc_cli/dual_mode_wallet_adapter.py rename to cli/dual_mode_wallet_adapter.py diff --git a/cli/aitbc_cli/imports.py b/cli/imports.py similarity index 100% rename from cli/aitbc_cli/imports.py rename to cli/imports.py diff --git a/cli/aitbc_cli/kyc_aml_providers.py b/cli/kyc_aml_providers.py similarity index 100% rename from cli/aitbc_cli/kyc_aml_providers.py rename to cli/kyc_aml_providers.py diff --git a/cli/aitbc_cli/main.py b/cli/main.py similarity index 79% rename from cli/aitbc_cli/main.py rename to cli/main.py index 1e3be39c..6425b95d 100755 --- a/cli/aitbc_cli/main.py +++ b/cli/main.py @@ -7,8 +7,8 @@ import click import sys from typing import Optional -from . import __version__ -from .config import get_config +from __init__ import __version__ +from config import get_config def with_role(role: str): @@ -20,58 +20,58 @@ def with_role(role: str): return func(ctx, *args, **kwargs) return wrapper return decorator -from .utils import output, setup_logging -from .commands.client import client -from .commands.miner import miner -from .commands.wallet import wallet -from .commands.auth import auth -from .commands.blockchain import blockchain -from .commands.marketplace import marketplace -from .commands.simulate import simulate -from .commands.admin import admin -from .commands.config import config -from .commands.monitor import monitor -from .commands.governance import governance -from .commands.exchange import exchange -from .commands.oracle import oracle -from .commands.market_maker import market_maker -from .commands.multisig import multisig -from .commands.genesis_protection import genesis_protection -from .commands.transfer_control import transfer_control -from .commands.agent import agent -from .commands.multimodal import multimodal -from .commands.optimize import optimize -# from .commands.openclaw import openclaw # Temporarily disabled due to naming conflict -from .commands.marketplace_advanced import advanced # Re-enabled after fixing registration issues -from .commands.swarm import swarm -from .commands.chain import chain -from .commands.genesis import genesis -from .commands.keystore import keystore -from .commands.test_cli import test -from .commands.node import node -from .commands.analytics import analytics -from .commands.agent_comm import agent_comm -from .commands.deployment import deploy -from .commands.cross_chain import cross_chain -from .commands.compliance import compliance -from .commands.surveillance import surveillance -from .commands.regulatory import regulatory -from .commands.ai_trading import ai_trading -from .commands.advanced_analytics import advanced_analytics_group -from .commands.ai_surveillance import ai_surveillance_group +from utils import output, setup_logging +from commands.client import client +from commands.miner import miner +from commands.wallet import wallet +from commands.auth import auth +from commands.blockchain import blockchain +from commands.marketplace import marketplace +from commands.simulate import simulate +from commands.admin import admin +from commands.config import config +from commands.monitor import monitor +from commands.governance import governance +from commands.exchange import exchange +from commands.oracle import oracle +from commands.market_maker import market_maker +from commands.multisig import multisig +from commands.genesis_protection import genesis_protection +from commands.transfer_control import transfer_control +from commands.agent import agent +from commands.multimodal import multimodal +from commands.optimize import optimize +# from commands.openclaw import openclaw # Temporarily disabled due to naming conflict +from commands.marketplace_advanced import advanced # Re-enabled after fixing registration issues +from commands.swarm import swarm +from commands.chain import chain +from commands.genesis import genesis +from commands.keystore import keystore +from commands.test_cli import test +from commands.node import node +from commands.analytics import analytics +from commands.agent_comm import agent_comm +from commands.deployment import deploy +from commands.cross_chain import cross_chain +from commands.compliance import compliance +from commands.surveillance import surveillance +from commands.regulatory import regulatory +from commands.ai_trading import ai_trading +from commands.advanced_analytics import advanced_analytics_group +from commands.ai_surveillance import ai_surveillance_group # AI provider commands -from .commands.ai import ai_group +from commands.ai import ai_group # Enterprise integration (optional) try: - from .commands.enterprise_integration import enterprise_integration_group + from commands.enterprise_integration import enterprise_integration_group except ImportError: enterprise_integration_group = None -from .commands.sync import sync -from .commands.explorer import explorer -from .plugins import plugin, load_plugins +from commands.sync import sync +from commands.explorer import explorer +from plugins import plugin, load_plugins @click.group() diff --git a/cli/aitbc_cli/main_minimal.py b/cli/main_minimal.py similarity index 100% rename from cli/aitbc_cli/main_minimal.py rename to cli/main_minimal.py diff --git a/cli/aitbc_cli/models/__init__.py b/cli/models/__init__.py similarity index 100% rename from cli/aitbc_cli/models/__init__.py rename to cli/models/__init__.py diff --git a/cli/aitbc_cli/models/chain.py b/cli/models/chain.py similarity index 100% rename from cli/aitbc_cli/models/chain.py rename to cli/models/chain.py diff --git a/cli/aitbc_cli/plugins.py b/cli/plugins.py similarity index 100% rename from cli/aitbc_cli/plugins.py rename to cli/plugins.py diff --git a/cli/aitbc_cli/security/__init__.py b/cli/security/__init__.py similarity index 100% rename from cli/aitbc_cli/security/__init__.py rename to cli/security/__init__.py diff --git a/cli/aitbc_cli/security/translation_policy.py b/cli/security/translation_policy.py similarity index 100% rename from cli/aitbc_cli/security/translation_policy.py rename to cli/security/translation_policy.py diff --git a/cli/setup.py b/cli/setup.py index 16cd7a31..59a29479 100755 --- a/cli/setup.py +++ b/cli/setup.py @@ -57,12 +57,12 @@ setup( }, entry_points={ "console_scripts": [ - "aitbc=aitbc_cli.main:main", + "aitbc=main:main", ], }, include_package_data=True, package_data={ - "aitbc_cli": ["*.yaml", "*.yml", "*.json"], + "": ["*.yaml", "*.yml", "*.json"], }, zip_safe=False, ) diff --git a/cli/aitbc_cli/utils/__init__.py b/cli/utils/__init__.py similarity index 100% rename from cli/aitbc_cli/utils/__init__.py rename to cli/utils/__init__.py diff --git a/cli/aitbc_cli/utils/crypto_utils.py b/cli/utils/crypto_utils.py similarity index 100% rename from cli/aitbc_cli/utils/crypto_utils.py rename to cli/utils/crypto_utils.py diff --git a/cli/aitbc_cli/utils/secure_audit.py b/cli/utils/secure_audit.py similarity index 100% rename from cli/aitbc_cli/utils/secure_audit.py rename to cli/utils/secure_audit.py diff --git a/cli/aitbc_cli/utils/security.py b/cli/utils/security.py similarity index 100% rename from cli/aitbc_cli/utils/security.py rename to cli/utils/security.py diff --git a/cli/aitbc_cli/utils/subprocess.py b/cli/utils/subprocess.py similarity index 100% rename from cli/aitbc_cli/utils/subprocess.py rename to cli/utils/subprocess.py diff --git a/cli/aitbc_cli/wallet_daemon_client.py b/cli/wallet_daemon_client.py similarity index 100% rename from cli/aitbc_cli/wallet_daemon_client.py rename to cli/wallet_daemon_client.py diff --git a/cli/aitbc_cli/wallet_migration_service.py b/cli/wallet_migration_service.py similarity index 100% rename from cli/aitbc_cli/wallet_migration_service.py rename to cli/wallet_migration_service.py