Clean rewrite into full-scale operations platform. Single proto/ root, monitoring_service removed, CLI + MCP + cockpit TUI with purple/cyan theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
29 KiB
fxt 2.0 — Foxhunt Operations Platform Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Clean rewrite of fxt into a full-scale monitoring and operations CLI with JSON output, MCP server, and cockpit TUI — using a single consolidated proto directory.
Architecture: fxt binary with three interfaces (CLI, MCP, TUI) sharing a unified gRPC client layer compiled from proto/ at workspace root. API Gateway is the single endpoint. monitoring_service is removed.
Tech Stack: Rust, tonic 0.14, clap 4.5, ratatui 0.29, serde_json, crossterm 0.28
Working directory: /home/jgrusewski/Work/foxhunt/.claude/worktrees/fxt-overhaul
Build command: SQLX_OFFLINE=true cargo check -p fxt 2>&1
Test command: SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1
Clippy command: SQLX_OFFLINE=true cargo clippy -p fxt -- -D warnings 2>&1
Phase 1: Proto Consolidation (Tasks 1–3)
Task 1: Create workspace-root proto/ directory
Copy the authoritative service protos to proto/ at workspace root. Merge the two monitoring protos into one.
Files:
- Create:
proto/trading.proto(copy fromservices/trading_service/proto/trading.proto) - Create:
proto/config.proto(copy fromservices/trading_service/proto/config.proto) - Create:
proto/ml.proto(copy fromservices/trading_service/proto/ml.proto) - Create:
proto/risk.proto(copy fromservices/trading_service/proto/risk.proto) - Create:
proto/ml_training.proto(copy fromservices/ml_training_service/proto/ml_training.proto) - Create:
proto/broker_gateway.proto(copy fromservices/broker_gateway_service/proto/broker_gateway.proto) - Create:
proto/trading_agent.proto(copy fromservices/trading_agent_service/proto/trading_agent.proto) - Create:
proto/data_acquisition.proto(copy fromservices/data_acquisition_service/proto/data_acquisition.proto) - Create:
proto/config_service.proto(copy fromservices/api_gateway/proto/config_service.proto) - Create:
proto/health.proto(copy frombin/fxt/proto/health.proto) - Create:
proto/monitoring.proto(MERGED from monitoring_service + trading_service versions)
Step 1: Copy all non-conflicting protos
mkdir -p proto
cp services/trading_service/proto/trading.proto proto/
cp services/trading_service/proto/config.proto proto/
cp services/trading_service/proto/ml.proto proto/
cp services/trading_service/proto/risk.proto proto/
cp services/ml_training_service/proto/ml_training.proto proto/
cp services/broker_gateway_service/proto/broker_gateway.proto proto/
cp services/trading_agent_service/proto/trading_agent.proto proto/
cp services/data_acquisition_service/proto/data_acquisition.proto proto/
cp services/api_gateway/proto/config_service.proto proto/
cp bin/fxt/proto/health.proto proto/
Step 2: Create merged monitoring.proto
The merged proto combines:
- System health/alerting RPCs from
services/trading_service/proto/monitoring.proto(10 RPCs) - Training metrics RPCs from
services/monitoring_service/proto/monitoring.proto(3 RPCs) - All message types from both (no field number conflicts since they're separate messages)
Add these 3 RPCs to the existing service block in the trading_service version:
// Training Metrics (absorbed from monitoring_service)
rpc GetLiveTrainingMetrics(GetLiveTrainingMetricsRequest)
returns (GetLiveTrainingMetricsResponse);
rpc StreamTrainingMetrics(StreamTrainingMetricsRequest)
returns (stream GetLiveTrainingMetricsResponse);
rpc GetEpochHistory(GetEpochHistoryRequest)
returns (GetEpochHistoryResponse);
And append all training-related message types (TrainingSession, GpuSnapshot, EpochFinancialSnapshot, etc.) after the existing system health messages.
Add cpu_percent, memory_used_mb, memory_total_mb fields to GetLiveTrainingMetricsResponse (these are used by the watch TUI).
Step 3: Verify proto syntax
protoc --proto_path=proto --descriptor_set_out=/dev/null proto/*.proto 2>&1
Expected: No errors.
Step 4: Commit
git add proto/
git commit -m "feat: create consolidated proto/ directory at workspace root
Merge monitoring_service training metrics into trading_service system health
monitoring.proto. All 11 proto files now in one canonical location."
Task 2: Rewire all service build.rs files to reference proto/
Each service build.rs currently references proto/*.proto relative to its own directory. We need to update them to reference ../../proto/ (services are at services/<name>/).
Files to modify:
services/trading_service/build.rsservices/ml_training_service/build.rsservices/broker_gateway_service/build.rsservices/trading_agent_service/build.rsservices/data_acquisition_service/build.rsservices/monitoring_service/build.rs(will be deleted in Phase 2, but keep working for now)services/api_gateway/build.rs
Step 1: Update each build.rs
For each service, change proto paths from local (proto/<name>.proto) to root (../../proto/<name>.proto) and include path from proto to ../../proto.
Example for services/trading_service/build.rs:
// Before:
.compile_protos(&["proto/trading.proto"], &["proto"])?;
// After:
.compile_protos(&["../../proto/trading.proto"], &["../../proto"])?;
For services/api_gateway/build.rs, also remove the reference to ../../bin/fxt/proto/trading.proto (the fat-client proto). The API gateway should compile the same service protos. Keep the config_service.proto reference since that's also in proto/ now (../../proto/config_service.proto).
For cross-service references in build.rs files (e.g., trading_service compiling ml_training.proto, trading_agent_service compiling ml.proto), change to ../../proto/<name>.proto — they're all in the same root proto/ dir now.
Step 2: Update cargo:rerun-if-changed paths in each build.rs
Every println!("cargo:rerun-if-changed=...") must point to the new proto/ path.
Step 3: Verify all services compile
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
Expected: All services compile successfully.
Step 4: Delete old per-service proto directories
rm -rf services/trading_service/proto/
rm -rf services/ml_training_service/proto/
rm -rf services/broker_gateway_service/proto/
rm -rf services/trading_agent_service/proto/
rm -rf services/data_acquisition_service/proto/
rm -rf services/api_gateway/proto/
# Do NOT delete services/monitoring_service/proto/ yet (Phase 2)
# Do NOT delete bin/fxt/proto/ yet (Phase 1 Task 3)
Step 5: Verify workspace still compiles after deletion
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
Step 6: Commit
git add -A
git commit -m "refactor: rewire all service build.rs to proto/ root directory
Delete per-service proto/ directories. All services now compile from
the single canonical proto/ at workspace root."
Task 3: Rewire fxt build.rs to use proto/ root
Replace all fxt proto references with the workspace root protos.
Files:
- Modify:
bin/fxt/build.rs - Delete:
bin/fxt/proto/(entire directory — 8 fat-client proto files)
Step 1: Rewrite bin/fxt/build.rs
The new build.rs compiles all 11 protos from ../../proto/. Client-only (no server generation needed for fxt — except for E2E test mock servers).
fn main() -> Result<(), Box<dyn std::error::Error>> {
let proto_root = "../../proto";
tonic_prost_build::configure()
.build_server(true) // Required for E2E test mock servers
.build_client(true)
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.server_mod_attribute(".", "#[allow(unused_qualifications)]")
.client_mod_attribute(".", "#[allow(unused_qualifications)]")
.protoc_arg("--experimental_allow_proto3_optional")
.compile_protos(
&[
&format!("{proto_root}/trading.proto"),
&format!("{proto_root}/health.proto"),
&format!("{proto_root}/ml.proto"),
&format!("{proto_root}/config.proto"),
&format!("{proto_root}/ml_training.proto"),
&format!("{proto_root}/trading_agent.proto"),
&format!("{proto_root}/broker_gateway.proto"),
&format!("{proto_root}/monitoring.proto"),
&format!("{proto_root}/risk.proto"),
&format!("{proto_root}/data_acquisition.proto"),
&format!("{proto_root}/config_service.proto"),
],
&[proto_root],
)?;
for proto in &[
"trading", "health", "ml", "config", "ml_training",
"trading_agent", "broker_gateway", "monitoring", "risk",
"data_acquisition", "config_service",
] {
println!("cargo:rerun-if-changed={proto_root}/{proto}.proto");
}
Ok(())
}
Step 2: Delete old fxt proto directory
rm -rf bin/fxt/proto/
Step 3: Update fxt source imports
The generated Rust module paths will change because proto packages changed. For example:
- Old:
foxhunt::tli::TradingServiceClient(from packagefoxhunt.tli) - New:
trading::TradingServiceClient(from packagetrading) - Old:
foxhunt::ml::MlServiceClient(from packagefoxhunt.ml) - New:
ml::MlServiceClient(from packageml)
Update all tonic::include_proto!() calls and use statements across fxt source files.
Important: The new proto types have different field names/structures than the fat-client protos. For example, the service trading.proto has StreamOrders, StreamPositions, StreamMarketData RPCs instead of the fat-client's unified SubscribeMarketData.
This step will likely break many existing fxt commands. That's expected — we're doing a clean rewrite. Just make the build.rs + proto compilation work; the commands will be rewritten in Phase 3.
Step 4: Verify fxt compiles (lib.rs at minimum)
SQLX_OFFLINE=true cargo check -p fxt 2>&1 | head -50
At this point, expect compilation errors in command files (they reference old proto types). That's fine — we're rewriting those.
Step 5: Commit
git add -A
git commit -m "refactor: point fxt build.rs at proto/ root, delete fat-client protos
fxt now compiles from the same service protos as all backend services.
Existing command implementations will be rewritten to use new proto types."
Phase 2: Remove monitoring_service (Tasks 4–5)
Task 4: Absorb monitoring_service into API Gateway
Move the Prometheus scraping logic from monitoring_service into the API Gateway. The API Gateway will serve the monitoring RPCs directly (no proxy needed).
Files:
- Read:
services/monitoring_service/src/prometheus_client.rs(understand the Prometheus scraping) - Read:
services/monitoring_service/src/service.rs(understand the gRPC service impl) - Create:
services/api_gateway/src/monitoring_handler.rs(new: Prometheus client + monitoring service impl) - Modify:
services/api_gateway/src/main.rs(register the monitoring service) - Modify:
services/api_gateway/build.rs(compile monitoring.proto with server=true)
Step 1: Read the monitoring_service source
Understand prometheus_client.rs and service.rs to know what to port.
Step 2: Create monitoring_handler.rs in api_gateway
Port the Prometheus scraping and monitoring service implementation. Key pieces:
PrometheusClientstruct: fetches fromPROMETHEUS_URLenv varMonitoringServiceImpl: implements the 3 training metrics RPCs- The merged proto now has both system health RPCs (10) and training RPCs (3)
- For system health RPCs: implement with real service health checks (call each backend service's health endpoint)
- For training RPCs: port the Prometheus scraping logic
Step 3: Register monitoring service in API Gateway main.rs
Add the monitoring service to the tonic server builder alongside existing services.
Step 4: Verify API Gateway compiles
SQLX_OFFLINE=true cargo check -p api_gateway 2>&1 | head -30
Step 5: Commit
git add services/api_gateway/
git commit -m "feat: absorb monitoring service into API Gateway
Port Prometheus scraping and training metrics RPCs into api_gateway.
API Gateway now serves monitoring RPCs directly without proxy."
Task 5: Delete monitoring_service and update infrastructure
Files:
- Delete:
services/monitoring_service/(entire directory) - Modify:
Cargo.toml(remove from workspace members) - Modify:
infra/k8s/gitlab/tailscale-proxy.yaml(remove monitoring path routing) - Modify:
.gitlab-ci.yml(remove monitoring_service compile/deploy jobs) - Delete:
proto/monitoring_service_standalone.protoif any leftovers - Modify: K8s deployment manifest for monitoring-service (if exists)
Step 1: Remove from workspace
In Cargo.toml, remove "services/monitoring_service" from workspace.members.
Step 2: Delete the service directory
rm -rf services/monitoring_service/
Step 3: Remove nginx monitoring path routing
In infra/k8s/gitlab/tailscale-proxy.yaml, remove the location /monitoring.MonitoringService/ block from the api.fxhnt.ai server. All monitoring RPCs now go to API Gateway's default location.
Step 4: Update CI pipeline
In .gitlab-ci.yml, remove any monitoring_service compile and deploy job references.
Step 5: Remove K8s deployment if it exists
Search for monitoring-service K8s manifests and delete them.
Step 6: Verify workspace compiles
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
Step 7: Commit
git add -A
git commit -m "chore: delete monitoring_service — absorbed into API Gateway
Remove from workspace members, delete K8s deployment, remove nginx routing.
Training metrics and system health now served directly by API Gateway."
Phase 3: New fxt Binary Skeleton (Tasks 6–9)
Task 6: Clean slate — strip fxt to minimal skeleton
Remove all existing command implementations and start fresh with the new command tree. Keep auth module and config module as reference (they're reusable).
Files:
- Modify:
bin/fxt/src/main.rs(new clap CLI definition) - Modify:
bin/fxt/src/lib.rs(new module declarations) - Modify:
bin/fxt/src/config.rs(keep, already has api_url) - Create:
bin/fxt/src/output.rs(JSON/human output abstraction) - Create:
bin/fxt/src/grpc.rs(unified gRPC client) - Delete:
bin/fxt/src/client/(old gRPC clients — will be replaced) - Delete:
bin/fxt/src/commands/(all old commands — will be rewritten) - Keep:
bin/fxt/src/auth/(reusable) - Keep:
bin/fxt/src/error.rs(reusable)
Step 1: Create output.rs — JSON/human output abstraction
Every command returns a CommandResult that can be rendered as JSON or human-readable:
use serde::Serialize;
/// Output format selected by --json flag
#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Human,
Json,
}
/// Render any serializable result in the chosen format
pub fn render<T: Serialize + HumanReadable>(result: &T, format: OutputFormat) -> anyhow::Result<()> {
match format {
OutputFormat::Json => {
let json = serde_json::to_string_pretty(result)?;
println!("{json}");
}
OutputFormat::Human => {
result.print_human();
}
}
Ok(())
}
/// Trait for human-readable rendering
pub trait HumanReadable {
fn print_human(&self);
}
Step 2: Create grpc.rs — unified gRPC client factory
use tonic::transport::Channel;
use anyhow::Result;
/// Create a gRPC channel to the API endpoint
pub async fn connect(api_url: &str) -> Result<Channel> {
let channel = Channel::from_shared(api_url.to_owned())?
.connect()
.await?;
Ok(channel)
}
All service clients are created from this single channel.
Step 3: Rewrite main.rs with new command tree
Define the full clap CLI structure with all commands and subcommands from the design doc. Each command is a stub that prints "not implemented yet". Add the global --json flag.
#[derive(Parser)]
#[command(name = "fxt", about = "Foxhunt Operations Platform")]
struct Cli {
/// API endpoint URL
#[arg(long = "api-url", env = "API_URL", default_value = "https://api.fxhnt.ai")]
api_url: String,
/// Output format (JSON for CI/LLM integration)
#[arg(long, short)]
json: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Auth(auth::AuthCommand),
Trade(trade::TradeCommand),
Train(train::TrainCommand),
Tune(tune::TuneCommand),
Model(model::ModelCommand),
Agent(agent::AgentCommand),
Backtest(backtest::BacktestCommand),
Broker(broker::BrokerCommand),
Data(data::DataCommand),
Service(service::ServiceCommand),
Cluster(cluster::ClusterCommand),
Risk(risk::RiskCommand),
Config(config_cmd::ConfigCommand),
Watch(watch::WatchCommand),
Mcp(mcp::McpCommand),
}
Step 4: Create stub command modules
Create bin/fxt/src/commands/ with one file per command group. Each file defines the subcommand enum and a stub execute() function:
bin/fxt/src/commands/mod.rsbin/fxt/src/commands/auth.rsbin/fxt/src/commands/trade.rsbin/fxt/src/commands/train.rsbin/fxt/src/commands/tune.rsbin/fxt/src/commands/model.rsbin/fxt/src/commands/agent.rsbin/fxt/src/commands/backtest.rsbin/fxt/src/commands/broker.rsbin/fxt/src/commands/data.rsbin/fxt/src/commands/service.rsbin/fxt/src/commands/cluster.rsbin/fxt/src/commands/risk.rsbin/fxt/src/commands/config_cmd.rsbin/fxt/src/commands/watch.rs(stub for now, Phase 5)bin/fxt/src/commands/mcp.rs(stub for now, Phase 6)
Step 5: Verify fxt compiles and runs help
SQLX_OFFLINE=true cargo check -p fxt 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- --help 2>&1
Step 6: Write basic CLI tests
Test that --help works, --json flag is parsed, each subcommand is recognized.
Step 7: Run tests
SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1
Step 8: Commit
git add -A
git commit -m "feat: fxt 2.0 skeleton — new command tree with 15 command groups
Clean rewrite with unified gRPC client, JSON/human output abstraction,
and stub implementations for all commands."
Task 7: Implement grpc.rs — full client layer
Build the unified gRPC client that creates typed service clients from a single channel.
Files:
- Modify:
bin/fxt/src/grpc.rs
Step 1: Write the client factory
pub struct FoxhuntClient {
channel: Channel,
}
impl FoxhuntClient {
pub async fn connect(api_url: &str) -> Result<Self> { ... }
pub fn trading(&self) -> trading::trading_service_client::TradingServiceClient<Channel> { ... }
pub fn ml(&self) -> ml::ml_service_client::MlServiceClient<Channel> { ... }
pub fn ml_training(&self) -> ml_training::ml_training_service_client::MlTrainingServiceClient<Channel> { ... }
pub fn broker(&self) -> broker_gateway::broker_gateway_service_client::BrokerGatewayServiceClient<Channel> { ... }
pub fn trading_agent(&self) -> trading_agent::trading_agent_service_client::TradingAgentServiceClient<Channel> { ... }
pub fn monitoring(&self) -> monitoring::monitoring_service_client::MonitoringServiceClient<Channel> { ... }
pub fn config(&self) -> config::configuration_service_client::ConfigurationServiceClient<Channel> { ... }
pub fn data_acquisition(&self) -> data_acquisition::data_acquisition_service_client::DataAcquisitionServiceClient<Channel> { ... }
pub fn risk(&self) -> risk::risk_service_client::RiskServiceClient<Channel> { ... }
}
The exact client type names depend on the generated code from the service protos. Read the generated code (target/debug/build/fxt-*/out/) to find the correct module paths.
Step 2: Test client construction
Write a unit test that creates a client (can't connect without a server, but can verify construction doesn't panic).
Step 3: Commit
git add bin/fxt/src/grpc.rs
git commit -m "feat: unified FoxhuntClient with typed service accessors"
Task 8: Implement service command (first real command)
The service command group is the most useful for ops. Implement it first as the template for all other commands.
Files:
- Modify:
bin/fxt/src/commands/service.rs
Step 1: Define the ServiceCommand enum
#[derive(Parser)]
pub struct ServiceCommand {
#[command(subcommand)]
action: ServiceAction,
}
#[derive(Subcommand)]
enum ServiceAction {
/// List all services with health status
List,
/// Get detailed status of a specific service
Status { service: String },
/// Health check all services
Health,
}
Step 2: Implement list subcommand
Calls monitoring_client.get_system_status() → renders as table (human) or JSON.
Step 3: Implement health subcommand
Calls monitoring_client.get_health_check() → renders per-service health.
Step 4: Implement status subcommand
Calls monitoring_client.get_system_status() with filter → renders detailed view.
Step 5: Test with --json output
Both human-readable and JSON output paths should work.
Step 6: Commit
git add bin/fxt/src/commands/service.rs
git commit -m "feat: fxt service list/status/health with --json support"
Task 9: Implement remaining CLI commands
Follow the same pattern as Task 8 for each command group. Each command:
- Defines clap subcommands
- Creates the appropriate gRPC client from
FoxhuntClient - Makes the RPC call
- Renders result via
output::render()(JSON or human)
Command implementation order (most useful first):
train— start/stop/status/list/logs (uses ml_training client)tune— start/stop/status/approve (uses ml_training client)model— list/status/predict/ensemble/promote (uses ml client)trade— submit/cancel/positions/orders/account (uses trading + broker clients)broker— status/connect/executions (uses broker_gateway client)agent— start/stop/status/config (uses trading_agent client)backtest— run/status/results (uses trading client's backtesting RPCs)data— download/status/cache/feeds (uses data_acquisition client)risk— status/limits/drawdown/stress/emergency (uses risk client)config— get/set/export/env (uses config client)cluster— status/resources/events (uses monitoring client + kubectl)auth— login/logout/status (port from existing auth module)
Each command should be its own commit. Each commit should include:
- The command implementation
- At least one test (CLI arg parsing)
- Verify
cargo checkandcargo clippypass
Phase 4: TUI Cockpits (Tasks 10–14)
Task 10: TUI foundation — theme, state, event loop
Build the cockpit TUI framework before adding individual cockpit views.
Files:
- Create:
bin/fxt/src/tui/mod.rs - Create:
bin/fxt/src/tui/theme.rs(color constants from logo) - Create:
bin/fxt/src/tui/state.rs(global TUI state) - Create:
bin/fxt/src/tui/event_loop.rs(event handling + gRPC streams) - Create:
bin/fxt/src/tui/cockpit.rs(cockpit trait + registry) - Modify:
bin/fxt/src/commands/watch.rs(wire up TUI launch)
Step 1: Create theme.rs
Define the color palette from the foxhunt logo:
use ratatui::style::Color;
pub const BG: Color = Color::Rgb(15, 15, 35); // #0F0F23
pub const SURFACE: Color = Color::Rgb(26, 26, 46); // #1A1A2E
pub const TEXT: Color = Color::Rgb(224, 224, 224); // #E0E0E0
pub const PRIMARY: Color = Color::Rgb(139, 92, 246); // #8B5CF6 purple
pub const ACCENT: Color = Color::Rgb(6, 182, 212); // #06B6D4 cyan
pub const SUCCESS: Color = Color::Rgb(16, 185, 129); // #10B981 green
pub const WARNING: Color = Color::Rgb(245, 158, 11); // #F59E0B amber
pub const ERROR: Color = Color::Rgb(239, 68, 68); // #EF4444 red
pub const MUTED: Color = Color::Rgb(107, 114, 128); // #6B7280 gray
pub const BORDER: Color = Color::Rgb(76, 29, 149); // #4C1D95 purple dim
pub const HIGHLIGHT: Color = Color::Rgb(34, 211, 238); // #22D3EE bright cyan
Step 2: Create cockpit trait
pub trait Cockpit {
fn name(&self) -> &str;
fn key(&self) -> char; // '1' through '6'
fn render(&self, frame: &mut Frame, area: Rect, state: &AppState);
}
Step 3: Create AppState
Hold all data for all cockpits. Updated by background gRPC streams.
Step 4: Create event loop
- Terminal setup/teardown (crossterm)
- Key handling: 1-6 switch cockpits, q quit, ? help
- Background tasks: gRPC streaming subscriptions (monitoring, executions, training)
- 1-second tick for gauge refresh
Step 5: Wire up watch command
fxt watch launches the TUI event loop.
Step 6: Verify compiles and basic TUI starts
SQLX_OFFLINE=true cargo check -p fxt 2>&1
Step 7: Commit
git add bin/fxt/src/tui/ bin/fxt/src/commands/watch.rs
git commit -m "feat: TUI foundation — theme, state, event loop, cockpit trait"
Task 11: Cockpit 1 — Overview
Four quadrants: Services | Resources | Training | Portfolio.
Files:
- Create:
bin/fxt/src/tui/cockpits/mod.rs - Create:
bin/fxt/src/tui/cockpits/overview.rs
Step 1: Implement overview cockpit
Layout: 2x2 grid using Layout::default().direction(Direction::Vertical) split into top/bottom, each split horizontally.
- Top-left: Service list (name + status indicator)
- Top-right: System resources (CPU/RAM/GPU gauges)
- Bottom-left: Active training summary (model, epoch, loss, Sharpe)
- Bottom-right: Portfolio summary (equity, P&L, positions, margin)
Use the theme colors for all widgets.
Step 2: Commit
Task 12: Cockpit 2 — Training
Files:
- Create:
bin/fxt/src/tui/cockpits/training.rs
Active sessions table + GPU panel + CPU/RAM panel + Loss sparkline + Epoch financial metrics.
Task 13: Cockpit 3–6 — Trading, Services, Risk, Data
Files:
- Create:
bin/fxt/src/tui/cockpits/trading.rs - Create:
bin/fxt/src/tui/cockpits/services.rs - Create:
bin/fxt/src/tui/cockpits/risk.rs - Create:
bin/fxt/src/tui/cockpits/data.rs
Implement each cockpit according to the design doc layouts.
Task 14: TUI integration test
Write an E2E test that launches the TUI with a mock gRPC server, verifies all 6 cockpits render without panicking, and responds to key events.
Phase 5: MCP Server Mode (Tasks 15–16)
Task 15: MCP protocol implementation
Files:
- Create:
bin/fxt/src/mcp/mod.rs - Create:
bin/fxt/src/mcp/server.rs(JSON-RPC 2.0 stdin/stdout) - Create:
bin/fxt/src/mcp/tools.rs(tool definitions) - Modify:
bin/fxt/src/commands/mcp.rs(wire up serve command)
Step 1: Implement MCP JSON-RPC protocol
Read JSON-RPC requests from stdin, dispatch to tool handlers, write responses to stdout.
Protocol messages:
initialize→ return server info + tool listtools/list→ return all available toolstools/call→ execute a tool and return result
Step 2: Define tool registry
Map each CLI command to an MCP tool:
Tool { name: "fxt_service_list", description: "List all services with health status", input_schema: {} }
Tool { name: "fxt_train_start", description: "Start ML training job", input_schema: { model: string, symbol: string } }
// ... one per CLI subcommand
Step 3: Wire up serve command
fxt mcp serve starts the stdin/stdout loop.
Step 4: Test with mock input
Write a test that pipes JSON-RPC requests and verifies responses.
Step 5: Commit
Task 16: MCP tool implementations
Each MCP tool reuses the same logic as the CLI command, but returns structured JSON instead of printing.
Refactor each command's execute() to return a serde-serializable result, then:
- CLI path: calls
output::render(result, format) - MCP path: serializes result as JSON-RPC response
This is where the --json architecture pays off — both CLI and MCP use the same serialization.
Phase 6: Final Verification (Task 17)
Task 17: Full verification and cleanup
Step 1: Run full workspace check
SQLX_OFFLINE=true cargo check --workspace 2>&1
Step 2: Run fxt tests
SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1
Step 3: Run clippy
SQLX_OFFLINE=true cargo clippy -p fxt -- -D warnings 2>&1
Step 4: Run fxt --help to verify command tree
SQLX_OFFLINE=true cargo run -p fxt -- --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- service --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- watch --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- mcp --help 2>&1
Step 5: Verify --json flag works
SQLX_OFFLINE=true cargo run -p fxt -- service list --json 2>&1
Step 6: Final commit
git add -A
git commit -m "feat: fxt 2.0 complete — operations platform with CLI, TUI cockpits, MCP server"