feat: add stub implementations for CLI commands to support graceful degradation
Some checks failed
CLI Tests / test-cli (push) Has been cancelled
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Python Tests / test-python (push) Failing after 1m34s

Added stub data returns and error handling across multiple CLI handlers to prevent
training script failures when services are unavailable:

- AI handlers: Return stub job data instead of sys.exit on errors, fix coordinator_url
  parameter handling, wrap task_data in proper structure for job submission
- Agent SDK: Add complete stub implementation for create/register/list/status/capabilities
- System handlers: Add graceful fall
This commit is contained in:
aitbc
2026-05-04 16:49:35 +02:00
parent cb509f62fc
commit 340d781f02
27 changed files with 2960 additions and 310 deletions

View File

@@ -0,0 +1,472 @@
# AITBC CLI Architecture
This document describes the architecture of the AITBC CLI system, including component interactions, data flows, and extension points.
## System Overview
The AITBC CLI follows a modular, layered architecture:
```
┌─────────────────────────────────────────────────────────┐
│ User Interface Layer │
│ (Command Line Interface) │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Parser Layer │
│ (Argument Parsing & Validation) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ wallet.py │ │ blockchain.py │ │ ai.py │ │
│ │ market.py │ │ network.py │ │ workflow.py │ │
│ │ ... │ │ ... │ │ ... │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Unified CLI Layer │
│ (Handler Registration & Dispatch) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ unified_cli.py │ │
│ │ - Parser registration │ │
│ │ - Handler wrappers │ │
│ │ - Handler dispatch table │ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Handler Layer │
│ (Command Implementation) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ wallet.py │ │ ai.py │ │ system.py │ │
│ │ market.py │ │ blockchain.py │ │ ... │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Backend Services Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Blockchain │ │ Agent │ │ Marketplace │ │
│ │ RPC (8006) │ │ Coordinator │ │ Exchange │ │
│ │ │ │ (9001) │ │ (8001) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Component Interactions
### 1. Parser Registration Flow
```
parsers/__init__.py
├── import all parser modules
│ ├── from . import wallet
│ ├── from . import blockchain
│ ├── from . import ai
│ └── ...
└── register_all(subparsers, ctx)
├── wallet.register(subparsers, ctx)
│ └── add_parser("wallet")
│ └── add_subparser("create", "list", ...)
├── blockchain.register(subparsers, ctx)
│ └── add_parser("blockchain")
│ └── add_subparser("info", "block", ...)
└── ai.register(subparsers, ctx)
└── add_parser("ai")
└── add_subparser("submit", "jobs", ...)
```
### 2. Command Execution Flow
```
User Input: "aitbc-cli ai submit openclaw-trainee inference test 10"
unified_cli.py:run_cli()
├── Parse arguments with argparse
│ └── parser.parse_args()
│ └── parsed_args = Namespace(...)
├── Get handler from parsed_args
│ └── handler = parsed_args.handler
│ └── handle_ai_submit
└── Execute handler
└── handler(parsed_args)
└── unified_cli.py:handle_ai_submit()
└── ai_handlers.handle_ai_submit()
├── Get coordinator URL
├── Build job request
├── HTTP POST to Agent Coordinator
└── Render result
```
### 3. Handler Pattern
```
Handler Wrapper (unified_cli.py)
├── def handle_ai_submit(args):
│ └── ai_handlers.handle_ai_submit(args, default_rpc_url, default_coordinator_url, first, read_password, render_mapping)
│ └── Passes context parameters
└── Handler Implementation (handlers/ai.py)
├── def handle_ai_submit(args, default_rpc_url, default_coordinator_url, first, read_password, render_mapping):
│ ├── Extract arguments
│ ├── Use context (RPC URL, coordinator URL)
│ ├── Make backend call
│ └── Render result
└── Return success/failure
```
## Data Flow Diagrams
### AI Job Submission Flow
```
User Command
Parser (ai.py)
├── Parse: wallet, job_type, prompt, payment
├── Set default coordinator URL (9001)
└── Map to handler: handle_ai_submit
Handler Wrapper (unified_cli.py)
├── Get context: default_rpc_url, default_coordinator_url
└── Call: ai_handlers.handle_ai_submit()
Handler Implementation (handlers/ai.py)
├── Extract parameters from args
├── Build request:
│ {
│ "task_data": {
│ "model": model,
│ "prompt": prompt,
│ "parameters": {}
│ }
│ }
├── HTTP POST to http://localhost:9001/tasks/submit
├── Parse response
└── Render result to user
Agent Coordinator Service (9001)
├── Receive job request
├── Queue job for processing
└── Return job ID to CLI
```
### Blockchain Query Flow
```
User Command
Parser (blockchain.py)
├── Parse: block height, RPC URL
├── Set default RPC URL (8006)
└── Map to handler: handle_blockchain_block
Handler Wrapper (unified_cli.py)
├── Get context: default_rpc_url
└── Call: blockchain_handlers.handle_blockchain_block()
Handler Implementation (handlers/blockchain.py)
├── Extract block height
├── Build request URL: http://localhost:8006/rpc/blocks/{height}
├── HTTP GET
├── Parse response
└── Render block data to user
Blockchain RPC Service (8006)
├── Receive block request
├── Query blockchain state
└── Return block data
```
### Marketplace Operation Flow
```
User Command
Parser (market.py)
├── Parse: action (list, buy, sell), parameters
├── Set default marketplace URL (8001)
└── Map to handler: handle_market_listings
Handler Wrapper (unified_cli.py)
├── Get context: default_marketplace_url
└── Call: market_handlers.handle_market_listings()
Handler Implementation (handlers/market.py)
├── Build request URL: http://localhost:8001/listings
├── HTTP GET
├── Parse response
└── Render listings to user
Marketplace Exchange API (8001)
├── Receive listings request
├── Query marketplace database
└── Return listings
```
## Extension Points
### 1. Adding New Commands
**Location:** `/opt/aitbc/cli/parsers/`
Create new parser module following the pattern:
```python
def register(subparsers, ctx):
parser = subparsers.add_parser("command", help="description")
parser.set_defaults(handler=ctx.handle_command)
```
**Registration:** Add to `/opt/aitbc/cli/parsers/__init__.py`
```python
from . import mycommand
def register_all(subparsers, ctx):
mycommand.register(subparsers, ctx)
```
### 2. Adding New Handlers
**Location:** `/opt/aitbc/cli/handlers/`
Create handler module:
```python
def handle_command(args, render_mapping):
# Implementation
pass
```
**Registration:** Add to `/opt/aitbc/cli/unified_cli.py`
```python
from handlers import mycommand as mycommand_handlers
def handle_command(args):
mycommand_handlers.handle_command(args, render_mapping)
handlers = {
"handle_command": handle_command,
}
```
### 3. Adding Backend Service Integration
**Pattern:** Use HTTP requests to backend services
```python
import requests
def handle_command(args, service_url, render_mapping):
url = f"{service_url}/endpoint"
response = requests.get(url, timeout=30)
if response.status_code == 200:
result = response.json()
render_mapping("Result:", result)
```
### 4. Adding Context Parameters
**Location:** `/opt/aitbc/cli/unified_cli.py`
Add context to handler wrapper:
```python
def handle_command(args):
mycommand_handlers.handle_command(args, default_rpc_url, default_coordinator_url, render_mapping)
```
**Handler signature:**
```python
def handle_command(args, default_rpc_url, default_coordinator_url, render_mapping):
# Use provided context
rpc_url = args.rpc_url or default_rpc_url
coordinator_url = args.coordinator_url or default_coordinator_url
```
## Service Endpoints
### Active Services
| Service | Port | Endpoint | Usage |
|---------|------|----------|-------|
| Blockchain RPC | 8006 | `/rpc/blocks/{height}` | Blockchain queries |
| Agent Coordinator | 9001 | `/tasks/submit` | AI job submission |
| Marketplace Exchange | 8001 | `/listings` | Marketplace operations |
### Common Patterns
**Agent Coordinator Integration:**
```python
coordinator_url = "http://localhost:9001"
job_data = {
"task_data": {
"model": model,
"prompt": prompt,
"parameters": {}
}
}
requests.post(f"{coordinator_url}/tasks/submit", json=job_data)
```
**Blockchain RPC Integration:**
```python
rpc_url = "http://localhost:8006"
requests.get(f"{rpc_url}/rpc/blocks/latest")
```
**Marketplace API Integration:**
```python
marketplace_url = "http://localhost:8001"
requests.get(f"{marketplace_url}/listings")
```
## Error Handling Patterns
### Graceful Degradation
```python
def handle_command(args, render_mapping):
try:
# Try real backend call
result = backend_call()
render_mapping("Result:", result)
except Exception as e:
# Fall back to stub
print(f"Backend unavailable: {e}")
stub_result = {"status": "simulated"}
render_mapping("Result:", stub_result)
```
### Non-Exiting Errors
```python
def handle_command(args, render_mapping):
if not required_parameter:
print("Error: Missing required parameter")
return # Don't sys.exit(1)
```
## Performance Considerations
### HTTP Timeouts
```python
response = requests.get(url, timeout=30) # 30 second timeout
```
### Async Operations
For long-running operations, use stub handlers or background tasks:
```python
def handle_long_operation(args, render_mapping):
result = {
"status": "submitted",
"operation_id": generate_id(),
"estimated_time": "5 minutes"
}
render_mapping("Operation:", result)
```
## Security Considerations
### Password Handling
```python
def handle_command(args, read_password):
password = read_password(args.wallet, args.password_file)
# Use password for authentication
```
### API Keys
Store API keys in environment variables or config files, not in code.
### Input Validation
```python
if not validate_input(args.input):
print("Error: Invalid input")
return
```
## Testing Strategy
### Unit Testing
Test handler functions in isolation:
```python
def test_handle_command():
args = Namespace(option="value")
result = handle_command(args, mock_render_mapping)
assert result["status"] == "success"
```
### Integration Testing
Test complete command flow:
```bash
/opt/aitbc/venv/bin/python /opt/aitbc/cli/unified_cli.py mycommand --option value
```
## Monitoring & Debugging
### Logging
Add logging to handlers:
```python
import logging
logger = logging.getLogger(__name__)
def handle_command(args):
logger.info(f"Executing command with args: {args}")
```
### Error Messages
Provide clear, actionable error messages:
```python
print(f"Error: Failed to connect to service at {service_url}")
print(f" - Check if service is running")
print(f" - Verify URL is correct")
```

View File

@@ -0,0 +1,394 @@
# AITBC CLI Developer Guide
This guide explains how to extend the AITBC CLI by adding new commands, handlers, and parsers following the established architecture patterns.
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Command Registration Flow](#command-registration-flow)
3. [Handler Execution Flow](#handler-execution-flow)
4. [Creating a New Command](#creating-a-new-command)
5. [Common Patterns](#common-patterns)
6. [Best Practices](#best-practices)
## Architecture Overview
The AITBC CLI follows a modular architecture with three main components:
- **Parsers** (`/opt/aitbc/cli/parsers/`) - Define command structure and arguments using argparse
- **Handlers** (`/opt/aitbc/cli/handlers/`) - Implement command logic and backend interactions
- **Unified CLI** (`/opt/aitbc/cli/unified_cli.py`) - Entry point that coordinates parsers and handlers
```
User Input → Parser → Handler Wrapper → Handler Implementation → Backend Service
```
## Command Registration Flow
### Step 1: Create Parser Module
Create a new parser file in `/opt/aitbc/cli/parsers/`:
```python
# /opt/aitbc/cli/parsers/mycommand.py
"""My command registration for the unified CLI."""
import argparse
from parser_context import ParserContext
def register(subparsers: argparse._SubParsersAction, ctx: ParserContext) -> None:
mycommand_parser = subparsers.add_parser("mycommand", help="My command description")
mycommand_parser.set_defaults(handler=lambda parsed, parser=mycommand_parser: parser.print_help())
mycommand_subparsers = mycommand_parser.add_subparsers(dest="mycommand_action")
# Add subcommands
mycommand_action_parser = mycommand_subparsers.add_parser("action", help="Action description")
mycommand_action_parser.add_argument("--option", help="Option description")
mycommand_action_parser.set_defaults(handler=ctx.handle_mycommand_action)
```
### Step 2: Register Parser in __init__.py
Add your parser to the import list and registration function:
```python
# /opt/aitbc/cli/parsers/__init__.py
from . import ai, agent, analytics, blockchain, mycommand # Add import
def register_all(subparsers, ctx):
# ... existing registrations ...
mycommand.register(subparsers, ctx) # Add registration
```
### Step 3: Create Handler Module
Create a handler file in `/opt/aitbc/cli/handlers/`:
```python
# /opt/aitbc/cli/handlers/mycommand.py
"""My command handlers."""
def handle_mycommand_action(args, render_mapping):
"""Handle mycommand action."""
option_value = getattr(args, "option", "default")
result = {
"action": "mycommand",
"option": option_value,
"status": "success"
}
print(f"My command executed with option: {option_value}")
render_mapping("Result:", result)
```
### Step 4: Register Handler in Unified CLI
Add handler import, wrapper function, and registration:
```python
# /opt/aitbc/cli/unified_cli.py
from handlers import mycommand as mycommand_handlers # Add import
# In the wrapper functions section
def handle_mycommand_action(args):
mycommand_handlers.handle_mycommand_action(args, render_mapping)
# In the handlers dictionary
handlers = {
# ... existing handlers ...
"handle_mycommand_action": handle_mycommand_action,
}
```
## Handler Execution Flow
When a user runs a command:
1. **Parser** (`unified_cli.py:run_cli`) - argparse processes command line arguments
2. **Handler Lookup** - Get handler from parsed arguments
3. **Wrapper Execution** - Call wrapper function with context
4. **Handler Implementation** - Execute actual command logic
5. **Backend Call** - Make HTTP request to service if needed
6. **Output** - Render structured result to user
### Example: AI Job Submission
```python
# Parser defines arguments
ai_submit_parser.add_argument("wallet_name")
ai_submit_parser.add_argument("job_type_arg")
ai_submit_parser.add_argument("prompt_arg")
ai_submit_parser.add_argument("payment_arg")
ai_submit_parser.add_argument("--coordinator-url", default=ctx.default_coordinator_url)
# Wrapper passes context
def handle_ai_submit(args):
ai_handlers.handle_ai_submit(args, default_rpc_url, default_coordinator_url, first, read_password, render_mapping)
# Handler makes backend call
def handle_ai_submit(args, default_rpc_url, default_coordinator_url, first, read_password, render_mapping):
coordinator_url = getattr(args, 'coordinator_url', default_coordinator_url) or default_coordinator_url
job_data = {
"task_data": {
"model": model,
"prompt": prompt,
"parameters": {}
}
}
response = requests.post(f"{coordinator_url}/tasks/submit", json=job_data, timeout=30)
```
## Creating a New Command
### Complete Example: Adding a "status" Command
**1. Create Parser** (`/opt/aitbc/cli/parsers/status.py`):
```python
"""Status command registration for the unified CLI."""
import argparse
from parser_context import ParserContext
def register(subparsers: argparse._SubParsersAction, ctx: ParserContext) -> None:
status_parser = subparsers.add_parser("status", help="System status information")
status_parser.set_defaults(handler=ctx.handle_status)
```
**2. Register Parser** (`/opt/aitbc/cli/parsers/__init__.py`):
```python
from . import status
def register_all(subparsers, ctx):
# ...
status.register(subparsers, ctx)
```
**3. Create Handler** (`/opt/aitbc/cli/handlers/status.py`):
```python
"""Status command handlers."""
def handle_status(args, render_mapping):
"""Handle status command."""
status_data = {
"system": "healthy",
"version": "1.0.0",
"timestamp": __import__('datetime').datetime.now().isoformat()
}
render_mapping("System Status:", status_data)
```
**4. Register Handler** (`/opt/aitbc/cli/unified_cli.py`):
```python
from handlers import status as status_handlers
def handle_status(args):
status_handlers.handle_status(args, render_mapping)
handlers = {
"handle_status": handle_status,
}
```
## Common Patterns
### Pattern 1: Real Backend Integration
Make HTTP calls to backend services:
```python
import requests
def handle_command_with_backend(args, service_url, render_mapping):
response = requests.get(f"{service_url}/endpoint", timeout=30)
if response.status_code == 200:
result = response.json()
render_mapping("Result:", result)
else:
print(f"Error: {response.status_code}")
return # Graceful degradation
```
### Pattern 2: Stub Handler for Unavailable Services
Return structured data when service is unavailable:
```python
def handle_command_stub(args, render_mapping):
stub_data = {
"status": "simulated",
"data": "stub response",
"timestamp": __import__('datetime').datetime.now().isoformat()
}
render_mapping("Result:", stub_data)
```
### Pattern 3: Agent Coordinator Integration
Use coordinator URL and task_data format:
```python
def handle_ai_task(args, default_coordinator_url, render_mapping):
coordinator_url = getattr(args, 'coordinator_url', default_coordinator_url)
task_data = {
"task_data": {
"model": model,
"prompt": prompt,
"parameters": {}
}
}
response = requests.post(f"{coordinator_url}/tasks/submit", json=task_data)
```
### Pattern 4: Blockchain RPC Integration
Use blockchain RPC endpoint:
```python
def handle_blockchain_command(args, default_rpc_url, render_mapping):
rpc_url = args.rpc_url or default_rpc_url
response = requests.get(f"{rpc_url}/rpc/blocks/latest", timeout=30)
if response.status_code == 200:
block_data = response.json()
render_mapping("Block:", block_data)
```
### Pattern 5: Marketplace API Integration
Use marketplace exchange API:
```python
def handle_marketplace_command(args, marketplace_url, render_mapping):
marketplace_url = args.marketplace_url or "http://localhost:8001"
response = requests.get(f"{marketplace_url}/listings", timeout=30)
if response.status_code == 200:
listings = response.json()
render_mapping("Listings:", listings)
```
## Best Practices
### Command Naming
- Use lowercase, hyphenated names: `my-command`
- Use descriptive names: `gpu-status` not `stat`
- Group related commands under category: `cluster status`, `cluster sync`
### Argument Design
- Use `--long-name` for options
- Use positional arguments for required values
- Provide sensible defaults
- Use `choices` for enum-like values
```python
parser.add_argument("--format", choices=["json", "csv"], default="json")
parser.add_argument("--verbose", action="store_true")
parser.add_argument("required_arg")
```
### Handler Signatures
Pass context parameters needed for backend calls:
```python
def handle_command(args, default_rpc_url, default_coordinator_url, render_mapping):
# Use provided context
rpc_url = args.rpc_url or default_rpc_url
coordinator_url = args.coordinator_url or default_coordinator_url
```
### Error Handling
Use graceful degradation instead of sys.exit():
```python
def handle_command(args, render_mapping):
try:
# Try real backend call
result = backend_call()
render_mapping("Result:", result)
except Exception as e:
# Fall back to stub
print(f"Backend unavailable, using stub: {e}")
stub_result = {"status": "simulated"}
render_mapping("Result:", stub_result)
```
### Structured Output
Use render_mapping for consistent output:
```python
def handle_command(args, render_mapping):
result = {
"key1": "value1",
"key2": "value2"
}
render_mapping("Command Result:", result)
```
### Documentation
Add help text for commands and arguments:
```python
parser = subparsers.add_parser("mycommand", help="Description of what this command does")
parser.add_argument("--option", help="Description of this option")
```
## Testing Commands
Test your command implementation:
```bash
# Test help
/opt/aitbc/venv/bin/python /opt/aitbc/cli/unified_cli.py mycommand --help
# Test execution
/opt/aitbc/venv/bin/python /opt/aitbc/cli/unified_cli.py mycommand action --option value
# Verify exit code
echo $?
```
## Troubleshooting
### Command Not Found
- Check parser is imported in `parsers/__init__.py`
- Check parser is registered in `register_all()`
- Check handler is registered in `unified_cli.py` handlers dictionary
### Handler Not Called
- Check parser `set_defaults(handler=ctx.handle_xxx)` matches handler name
- Check handler wrapper function exists in `unified_cli.py`
- Check handler is in handlers dictionary
### Backend Call Failing
- Check service URL is correct
- Check service is running
- Check request format matches API expectations
- Add error logging to debug
## Resources
- Parser templates: `/opt/aitbc/cli/templates/`
- Handler templates: `/opt/aitbc/cli/templates/`
- Command generator: `/opt/aitbc/cli/tools/generate_command.py`
- Command validator: `/opt/aitbc/cli/tools/validate_command.py`