Files
foxhunt/docs/plans/2026-03-03-fxt-overhaul-implementation.md
jgrusewski df93b4c534 docs: fxt 2.0 design and implementation plan
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>
2026-03-03 20:11:21 +01:00

863 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 13)
### 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 from `services/trading_service/proto/trading.proto`)
- Create: `proto/config.proto` (copy from `services/trading_service/proto/config.proto`)
- Create: `proto/ml.proto` (copy from `services/trading_service/proto/ml.proto`)
- Create: `proto/risk.proto` (copy from `services/trading_service/proto/risk.proto`)
- Create: `proto/ml_training.proto` (copy from `services/ml_training_service/proto/ml_training.proto`)
- Create: `proto/broker_gateway.proto` (copy from `services/broker_gateway_service/proto/broker_gateway.proto`)
- Create: `proto/trading_agent.proto` (copy from `services/trading_agent_service/proto/trading_agent.proto`)
- Create: `proto/data_acquisition.proto` (copy from `services/data_acquisition_service/proto/data_acquisition.proto`)
- Create: `proto/config_service.proto` (copy from `services/api_gateway/proto/config_service.proto`)
- Create: `proto/health.proto` (copy from `bin/fxt/proto/health.proto`)
- Create: `proto/monitoring.proto` (MERGED from monitoring_service + trading_service versions)
**Step 1: Copy all non-conflicting protos**
```bash
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:
```protobuf
// 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**
```bash
protoc --proto_path=proto --descriptor_set_out=/dev/null proto/*.proto 2>&1
```
Expected: No errors.
**Step 4: Commit**
```bash
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.rs`
- `services/ml_training_service/build.rs`
- `services/broker_gateway_service/build.rs`
- `services/trading_agent_service/build.rs`
- `services/data_acquisition_service/build.rs`
- `services/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`:
```rust
// 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**
```bash
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
```
Expected: All services compile successfully.
**Step 4: Delete old per-service proto directories**
```bash
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**
```bash
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
```
**Step 6: Commit**
```bash
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).
```rust
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**
```bash
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 package `foxhunt.tli`)
- New: `trading::TradingServiceClient` (from package `trading`)
- Old: `foxhunt::ml::MlServiceClient` (from package `foxhunt.ml`)
- New: `ml::MlServiceClient` (from package `ml`)
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)**
```bash
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**
```bash
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 45)
### 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:
- `PrometheusClient` struct: fetches from `PROMETHEUS_URL` env var
- `MonitoringServiceImpl`: 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**
```bash
SQLX_OFFLINE=true cargo check -p api_gateway 2>&1 | head -30
```
**Step 5: Commit**
```bash
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.proto` if 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**
```bash
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**
```bash
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30
```
**Step 7: Commit**
```bash
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 69)
### 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:
```rust
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**
```rust
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.
```rust
#[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.rs`
- `bin/fxt/src/commands/auth.rs`
- `bin/fxt/src/commands/trade.rs`
- `bin/fxt/src/commands/train.rs`
- `bin/fxt/src/commands/tune.rs`
- `bin/fxt/src/commands/model.rs`
- `bin/fxt/src/commands/agent.rs`
- `bin/fxt/src/commands/backtest.rs`
- `bin/fxt/src/commands/broker.rs`
- `bin/fxt/src/commands/data.rs`
- `bin/fxt/src/commands/service.rs`
- `bin/fxt/src/commands/cluster.rs`
- `bin/fxt/src/commands/risk.rs`
- `bin/fxt/src/commands/config_cmd.rs`
- `bin/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**
```bash
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**
```bash
SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1
```
**Step 8: Commit**
```bash
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**
```rust
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**
```bash
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**
```rust
#[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**
```bash
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:
1. Defines clap subcommands
2. Creates the appropriate gRPC client from `FoxhuntClient`
3. Makes the RPC call
4. Renders result via `output::render()` (JSON or human)
**Command implementation order** (most useful first):
1. `train` — start/stop/status/list/logs (uses ml_training client)
2. `tune` — start/stop/status/approve (uses ml_training client)
3. `model` — list/status/predict/ensemble/promote (uses ml client)
4. `trade` — submit/cancel/positions/orders/account (uses trading + broker clients)
5. `broker` — status/connect/executions (uses broker_gateway client)
6. `agent` — start/stop/status/config (uses trading_agent client)
7. `backtest` — run/status/results (uses trading client's backtesting RPCs)
8. `data` — download/status/cache/feeds (uses data_acquisition client)
9. `risk` — status/limits/drawdown/stress/emergency (uses risk client)
10. `config` — get/set/export/env (uses config client)
11. `cluster` — status/resources/events (uses monitoring client + kubectl)
12. `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 check` and `cargo clippy` pass
---
## Phase 4: TUI Cockpits (Tasks 1014)
### 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:
```rust
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**
```rust
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**
```bash
SQLX_OFFLINE=true cargo check -p fxt 2>&1
```
**Step 7: Commit**
```bash
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 36 — 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 1516)
### 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 list
- `tools/list` → return all available tools
- `tools/call` → execute a tool and return result
**Step 2: Define tool registry**
Map each CLI command to an MCP tool:
```rust
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**
```bash
SQLX_OFFLINE=true cargo check --workspace 2>&1
```
**Step 2: Run fxt tests**
```bash
SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1
```
**Step 3: Run clippy**
```bash
SQLX_OFFLINE=true cargo clippy -p fxt -- -D warnings 2>&1
```
**Step 4: Run fxt --help to verify command tree**
```bash
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**
```bash
SQLX_OFFLINE=true cargo run -p fxt -- service list --json 2>&1
```
**Step 6: Final commit**
```bash
git add -A
git commit -m "feat: fxt 2.0 complete — operations platform with CLI, TUI cockpits, MCP server"
```