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>
863 lines
29 KiB
Markdown
863 lines
29 KiB
Markdown
# 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 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 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:
|
||
- `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 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:
|
||
|
||
```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 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:
|
||
|
||
```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 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 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"
|
||
```
|