Skip to content

rust-mux 0.4.0: client-config discovery + 5-step wizard with strategy choice#1

Open
Szowesgad wants to merge 20 commits into
mainfrom
feat/update-and-modularize
Open

rust-mux 0.4.0: client-config discovery + 5-step wizard with strategy choice#1
Szowesgad wants to merge 20 commits into
mainfrom
feat/update-and-modularize

Conversation

@Szowesgad

Copy link
Copy Markdown
Contributor

Summary

Rebuilds the rust-mux wizard around the operator's intended flow. Source of truth flips from ps-scan to client config files (Claude/Codex/Junie/Gemini); five-step wizard replaces the old four-step model; three explicit strategies (Unified / Per-client / Auto-rewire DANGER) replace the legacy [SAFE GEN] / [MUX ONLY] / [CLIPBOARD] overlay.

What changed

Discovery layer

  • ps-scan demoted to enrichment-only (stamps PIDs, surfaces orphans)
  • scan::scan_hosts() + merge_services() drive STEP 1 and STEP 2
  • 12 canonical default_sources() across Claude Code/Desktop, Codex, Junie (×3 paths), Gemini, plus legacy editor hosts
  • Custom-path text input on STEP 1 (i to enter)

Strategies (STEP 3)

  • Unified ~/.config/mux/{config.toml, mcp.json, mcp.toml} with dedup + per-client startup snippets
  • Per-client → one mux config per originating client kind in that client's native format (claude.json, codex.toml, junie.json, …)
  • [DANGER] Auto-rewire → backup-first preview-first JSON/TOML rewrite of existing client configs with CONFIRM prompt and rollback commands; reuses the existing crate::danger plan/preview/execute machinery

Tray prompt (STEP 5)

  • `Yes — start now` spawns `rust-mux --tray --config ` detached. Persistent launchd-install deferred.

Branding + canonical paths

  • `rmcp_mux wizard` → `rust-mux wizard` everywhere
  • self-skip dedup bug fixed (`rust-mux` || `rust-mux` → `rust-mux` || `rmcp_mux`)
  • socket fallback now uses v0.4.0 canonical `~/.rmcp-servers/rust-mux/sockets/`
  • `runtime/status.rs` daemon-status banner + `multi_tui.rs` dashboard header rebrand (closed by marble round 4)

Docs

  • `docs/WIZARD.md` rewritten end-to-end for the 5-step flow
  • New `.vibecrafted/GUIDELINES.md` (canonical per-repo, agent-agnostic doctrine)
  • `AI_README.md` bumped to 0.4.0 / 2026-05-05

Marbles convergence
After my five `[claude/vc-implement]` commits, codex ran `vc-marbles --count 4 --depth 10` and produced four `marble:` commits closing real defects:

  • `95785fa` socket-allocation owner returned to mux_gen + path-traversal helpers
  • `3bf5611` per-client + danger strategies now honour STEP 2 server selection
  • `72d3586` per-client outputs no longer overwrite each other for same-kind sources (Junie x3, Cursor x2, VSCode x2)
  • `e9aa306` runtime identity + wizard summary alignment

Each `Gate: pass; Regressions: 0`.

Quality gates

`prview --remote-only feat/update-and-modularize main --with-tests --with-lint`:

Gate Status
Rustfmt PASS
Cargo check PASS
Clippy (-D warnings) PASS
Cargo test (`cargo test --all-targets --all-features`) PASS — 87 passed, 0 failed, 1 ignored
Cargo audit PASS — 0 vulnerabilities, 9 unmaintained advisories (GTK3 stack via tray-icon)
Semgrep scan PASS

`make test-full` (with `--ignored`) green: `mux_transport_roundtrip_with_loctree_mcp` runs against local `loctree-mcp v0.9.4` and passes.

prview verdict: PASS / MERGE WITH REVIEW — the four caveats are reviewer signals, not blockers (7 false-positive removed-symbol entries from the modular split heuristic, 33% file-name coverage that misses inline `#[cfg(test)]` modules, 1 informational SARIF entry, and the GTK3 unmaintained advisories).

Before-merge punch list

See the full vc-review report at `.vibecrafted/reports/` for evidence and recommendations. Six findings, none blocking:

  • (P2) Add AppState transition tests for `wizard/keys.rs` (5-step happy path + strategy → PendingAction routing)
  • (P2) Track `glib 0.18` RUSTSEC-2024-0429 (unsound, patched in glib >= 0.20); waiting on a tray-icon release that bumps the chain
  • (P3) Audit `src/lib.rs` + `src/runtime/status.rs` for `println!` on stdio transport path; convert to `tracing` if any leak there
  • (P3) Drop `#[allow(dead_code)]` in `wizard/services.rs` + `wizard/ui.rs` (helpers now consumed)
  • (P3) CHANGELOG entry covering the wizard rebuild + 4 marble fixes
  • (P3) `wizard::persist::start_tray_daemon` should prefer `std::env::current_exe()` over `"rust-mux"` on PATH

Test plan

  • `make wizard-dry-run` on a workstation with real client configs (`/.claude.json`, `/.codex/config.toml`, `~/.junie/mcp/mcp.json`); confirm STEP 1 shows N servers per source (no more "tutaj pusto"), STEP 2 dedup count makes sense, STEP 5 result text wraps cleanly on the operator's terminal width
  • `make wizard` (live, not dry-run) → Unified strategy → verify `~/.config/mux/{config.toml, mcp.json, mcp.toml}` are written with the expected services
  • `make wizard` → Per-client strategy → verify per-kind native files (`claude.json`, `codex.toml`, `junie.json`) get only that source's servers
  • `make wizard` → `[DANGER]` Auto-rewire on a throwaway test config → verify `..bak` is created, `CONFIRM` prompt blocks unintentional writes, rollback commands restore cleanly
  • STEP 5 `Yes — start now` spawns the tray daemon; verify with `rust-mux daemon-status`
  • `rust-mux` library consumers (default-features = false) still build clean

Szowesgad and others added 16 commits December 26, 2025 02:38
- Update paths from ~/.rmcp_servers/ to ~/.rmcp-servers/
- Fix command examples to use rmcp-memex (hyphenated binary name)

Created by M&K (c)2025 The LibraxisAI Team
- Default socket dir: ~/.rmcp-servers/rmcp-mux/sockets
- Proxy command: rmcp-mux-proxy (hyphenated)
- Service name default: rmcp-mux
- Update tests to use hyphenated names
- Support both old (rmcp_mux) and new (rmcp-mux) in detection

Created by M&K (c)2025 The LibraxisAI Team
…rrected per-client discovery defaults (Claude Code+Desktop, Codex, Junie, Gemini, generic ~/.ai/~/.agents); add src/mux_gen.rs producing ~/.config/mux/{mcp.json,mcp.toml,config.toml} with dedup+conflict detection; add src/danger.rs with backup-first preview-first JSON/TOML rewrite and rollback; rework wizard with safe vs [DANGER] paths plus custom imports; add docs/WIZARD.md and docs/vc-agents-client-discovery-plan.md; 83 tests green incl. parsing, dedup, mux config emission, backups, rollback. Gates: fmt+clippy -D warnings+tests all green.

Co-authored-by: Junie <junie@jetbrains.com>
… unreferenced src/runtime_legacy.rs and src/wizard_legacy.rs which were not consumed by lib.rs/main.rs and are not part of the modular runtime/wizard architecture described in vc-context-engine/plans/meta/2026-05-04_operator-runtime-family-platform.md. Plan refers exclusively to src/runtime/ and src/wizard/ as the active surface. Gates: cargo fmt --check, cargo clippy -D warnings, cargo test --all-targets --all-features all green (83 passed, 1 ignored).

Co-authored-by: Junie <junie@jetbrains.com>
- src/wizard/ui.rs: title bar reads "rust-mux wizard" (was the legacy
  "rmcp_mux wizard" leftover from the 0.3 → 0.4 rebrand). The "Already
  rewired to rmcp_mux" status line and the "rewired to use rmcp_mux
  proxy" details panel now read rust-mux / rust-mux-proxy.
- src/wizard/services.rs: self-skip in detect_running_mcp_servers had
  a copy/paste bug (`args.contains("rust-mux") || args.contains("rust-mux")`)
  that made the legacy `rmcp_mux` binary slip through and pollute the
  detected list. Now skips rust-mux *and* the legacy rmcp_mux name.
- src/wizard/services.rs: detected-process socket fallback now uses the
  v0.4.0 canonical layout (`~/.rmcp-servers/rust-mux/sockets/`) instead
  of the obsolete `~/mcp-sockets/`.
- src/wizard/{mod,types}.rs: doc-comment leftovers (`rmcp_mux services`,
  `use rmcp_mux`) updated to rust-mux.

Sanity baseline before the larger discovery + 5-step flow rebuild.
ps-scan stays in place for now; it is demoted to running-status
enrichment in the next commit and replaced with scan_hosts() as the
source of truth.

Gates: cargo fmt --check, cargo clippy --all-targets --all-features
-D warnings, cargo test --all-targets --all-features → 83 passed,
0 failed, 1 ignored.

Authored-By: claude <agents@vetcoders.io>
…LINES.md

Aligns the per-repo doc surface with the actual 0.4.0 code.

- AI_README.md: bumped from the stale "Version: 0.3.0 / 2025-12-04"
  header to "Version: 0.4.0 / 2026-05-05". Project-Structure section
  now reflects the modular runtime/ + wizard/ split, the new
  src/{mux_gen,danger,multi,multi_tui,tray_dashboard,common}.rs
  modules, and the bin/ split (rust-mux + rust-mux-proxy). Config
  section gains the heartbeat fields. CLI subcommands list adds
  daemon-status and dashboard. Testing section points at `make gates`
  / `make test-full` (the canonical Makefile surface) and gives the
  current 83-pass baseline. The dangling/duplicated mid-document
  fragment that referenced the old single-file structure is gone.

- .vibecrafted/GUIDELINES.md (new): canonical, agent-agnostic per-repo
  doctrine — same rules for Claude, Codex, Gemini, Junie. Covers
  identity (active runtime/wizard lines, deleted legacy modules),
  Makefile gate surface, Living Tree convention, structural map and
  known twins, wizard safe/danger doctrine, library API surface,
  v0.4.0 canonical paths, .env hygiene, commit format with AGENT
  FAIRNESS, init discipline, repo-specific anti-patterns.

- .gitignore: previous `/.vibecrafted` blanket rule blocked
  GUIDELINES.md from ever being tracked. Switched to a whitelist
  pattern that ignores everything under `.vibecrafted/` *except*
  GUIDELINES.md and the plans/reports symlinks (which point at the
  per-day artifact root in $HOME). tmp/ and ad-hoc prompts stay
  ignored.

- .vibecrafted/{plans,reports}: rotated symlinks to today's
  artifact root (2026_0506).

Gates: cargo fmt --check, cargo clippy --all-targets --all-features
-D warnings, cargo test --all-targets --all-features → 83 passed,
0 failed, 1 ignored (no code touched in this commit).

Authored-By: claude <agents@vetcoders.io>
Replaces ps-scan-as-discovery with client-config-as-discovery. The whole
"wizard sees zero servers because the agent wasn't running anything yet"
class of bugs goes away: source of truth is the user's actual MCP client
configs (Claude/Codex/Junie/Gemini/...), not what is currently spawned.

- src/wizard/services.rs: load_all_services rewritten to call
  scan::scan_hosts() across every well-known client config first, dedup
  via merge_services, then attribute each entry to its originating
  HostKind + path. Mux daemon config is loaded second and only adds
  entries not already discovered (client-config copy wins). ps-scan is
  demoted to enrich_running_state(): it stamps PIDs on matching entries
  and surfaces ps-only orphans with `ServiceSource::DetectedRunning`,
  but never drives discovery. The MCP_PATTERNS whitelist is now broader
  (covers `*-mcp-server`, `mcp-*`, `loctree-mcp`, `aicx-mcp`, `claude-mcp`)
  but only bounds the enrichment pass — misses there mean a missed PID
  badge, never a missing server.
- src/wizard/services.rs: new load_services_from_custom_path() returns
  entries tagged ServiceSource::Custom for the wizard custom-path input
  field that lands in the next commit.
- src/wizard/types.rs: ServiceSource enum reshaped — was Config/Detected,
  now Client { kind, path } / MuxConfig / Custom { path } / DetectedRunning.
  short_label / path / is_client helpers added (consumed by the next
  commit; flagged with #[allow(dead_code)] + Why comments).
- src/wizard/ui.rs: source indicator no longer renders bare [C]/[D];
  shows the per-kind tag ([claude]/[codex]/[junie]/[gemini]/[mux]/...)
  with kind-aware colour. PID badge now shows on any entry whose
  enrich_running_state matched, not just legacy ServiceSource::Detected.
- src/wizard/{mod,keys,persist}.rs: all ServiceSource::Config sites
  updated to ServiceSource::MuxConfig.
- New tests in services.rs: custom-path import attribution, missing-file
  short-circuit, services_from_merge attributes the first source kind,
  cmds_equivalent matches binary basenames.

Gates: cargo fmt --check, cargo clippy --all-targets --all-features
-D warnings, cargo test --all-targets --all-features → 87 passed, 0
failed, 1 ignored (was 83 → 87 with 4 new wizard::services tests).

Authored-By: claude <agents@vetcoders.io>
Rebuilds the wizard around the operator's intended flow: discover servers
from client configs (the actual source of truth), review them, pick a
strategy, confirm, and offer to start the tray daemon. Replaces the old
4-step ps-scan + service-editor flow that conflated runtime info with
configuration source of truth.

5-step flow:

  1. DiscoverySources — toggle which client configs to scan
     (~/.claude.json, ~/.codex/config.toml, ~/.gemini/settings.json,
     ~/.junie/, ~/.ai/, ~/.agents/, plus legacy editor hosts), with a
     custom-path text input for additional files.
  2. ServerReview — read-only tree of discovered servers grouped by
     originating client kind, with dedup count.
  3. StrategyChoice — Unified / Per-client / Auto-rewire (DANGER):
       Unified    → ~/.config/mux/{config.toml, mcp.json, mcp.toml}
                    with every selected server, deduplicated. Recommended.
       Per-client → ~/.config/mux/<kind>.{json,toml} per originating
                    client kind, native format per client. Daemon
                    config.toml still merged across every selected source.
       Auto-rewire (DANGER) → backup-first preview-first rewrite of the
                    operator's existing client configs to route through
                    rust-mux-proxy. Reuses the existing crate::danger
                    plan/preview/execute machinery.
  4. SummaryConfirm — preview of what will be written and where, then
     Confirm / Back / Cancel. Lists explicit output paths, the planned
     rewrite list (for AutoRewire), and surfaces ineligible sources
     (e.g. Gemini's settings.json — no verified strict-config flag).
  5. ResultAndTray — show the strategy result with per-client startup
     snippets, then offer to start a tray daemon now (spawns
     `rust-mux --tray --config <generated>` detached).

Code changes:

- src/wizard/types.rs rewritten:
  * WizardStep replaced with the 5-step enum.
  * Strategy { Unified, PerClient, AutoRewire } replaces ConfirmChoice
    (the old SafeGen / MuxOnly / Clipboard / Danger choices are gone).
  * SummaryAction { Confirm, Back, Cancel } and TrayChoice { StartNow, No }
    drive the new dialog steps.
  * SourceEntry / CustomPathInput hold the STEP 1 working set.
  * ServiceSource lost the unused MuxConfig variant; ServiceEntry lost
    the unused `dirty` field; AppState lost the unused `clients` field.
  * The legacy in-step service editor (Field/Panel/FormState) is gone.

- src/wizard/services.rs simplified:
  * Removed the legacy `load_all_services` + helpers (services_from_merge,
    svc_key, env_signature, configs_equivalent, client_first_then_name,
    default_server_config, form_from_service, service_from_form).
  * Exposed `build_services_from_scans(scans)` as the single shared
    helper consumed by keys.rs::advance_to_step2.
  * `enrich_running_state` and `load_services_from_custom_path` kept
    intact.

- src/wizard/keys.rs rewritten as a per-step dispatcher with handlers
  for each of the five steps. Custom-path edit mode lives on STEP 1
  (`i` to enter, Enter to commit, Esc to cancel). All step transitions
  now go through `update_step{N}_message` helpers so the status bar
  stays in sync.

- src/wizard/ui.rs rewritten with five new screens (sources list +
  custom-path panel, server-review tree + summary, strategy radio,
  summary confirm, and a result + tray prompt). Source indicators show
  the per-client kind tag with kind-aware colour.

- src/wizard/persist.rs renamed `run_safe_generate` →
  `run_unified_generate`, added `run_per_client_generate`, kept
  `run_danger_auto_configure` (drops the unused `_unused_sink` param),
  added `start_tray_daemon` that spawns `rust-mux --tray --config
  <generated>` detached. Test fixtures updated to the new AppState
  shape.

- src/wizard/mod.rs simplified: builds initial sources from
  `default_sources()` plus `--import-config` paths, drives the TUI
  loop, drains `PendingAction` after raw-mode is restored, and
  re-enters the alt screen on STEP 5 so the operator picks the tray
  prompt with the same TUI machinery.

- src/wizard/clients.rs deleted: detect_clients was already redundant
  (everything we need lives in `app.sources`), and
  client_entry_from_custom_path is replaced by the inline
  custom-path handling in keys.rs.

- src/mux_gen.rs gains build_per_client_outputs +
  write_per_client_outputs + per_client_instructions for the
  Per-client strategy. Each per-kind file is written in that client's
  native format (Codex → TOML mcp_servers, others → JSON mcpServers).

- 5 new tests (in wizard::persist + wizard::services) covering empty,
  dry-run, and origin-attribution paths.

Gates: cargo fmt --check, cargo clippy --all-targets --all-features
-D warnings, cargo test --all-targets --all-features → 86 passed, 0
failed, 1 ignored.

Authored-By: claude <agents@vetcoders.io>
…step flow

Aligns the doc surface with the 5-step wizard rebuild from the previous
commit. No code changes.

- docs/WIZARD.md fully rewritten:
  * 5-step flow walkthrough with the actual TUI screens (sources list +
    custom-path panel, server-review tree, strategy radio, summary +
    confirm, result + tray prompt).
  * Source-of-truth note: discovery runs from client config files, not
    ps-scan. ps-scan is enrichment-only.
  * Default-sources table covering Claude Code/Desktop, Codex, Junie
    (×3 paths), Gemini, plus legacy editor hosts.
  * Per-strategy details for Unified / Per-client / [DANGER] Auto-rewire,
    including the exact files written and the rollback shape.
  * CLI flags, troubleshooting (empty source list, invalid parse,
    skipped Gemini, missing CONFIRM prompt, tray spawn failure),
    "see also" links to integration.md and the discovery plan.
  * Tray-monitoring section consolidates the post-wizard CLI commands
    (rust-mux --tray, daemon-status, dashboard, health) that used to
    live in the older Polish-language guide.
  * The previous Polish content (Wymagania / Konfiguracja / Tray /
    Dashboard / Status JSON / Troubleshooting) is gone — its actionable
    bits are folded into the canonical English doc; per-repo
    instructions are English-only.

- AI_README.md: replaces "Three-Step Wizard" section with a concise
  "Five-Step Wizard" overview that reflects DiscoverySources →
  ServerReview → StrategyChoice → SummaryConfirm → ResultAndTray and
  the Unified/Per-client/Auto-rewire strategy split. Mentions the
  source-of-truth model (client configs, not ps).

- .vibecrafted/GUIDELINES.md: "Wizard / config doctrine" section
  rewritten to describe the strategy split (Unified / Per-client /
  Auto-rewire) and the source-of-truth rule. Anti-patterns list gains
  three new entries: don't reintroduce the old 4-step flow, don't
  reintroduce the [SAFE GEN]/[MUX ONLY]/[CLIPBOARD] confirm overlay,
  don't make the MCP_PATTERNS whitelist primary again.

Gates: cargo fmt --check, cargo clippy --all-targets --all-features
-D warnings, cargo test --all-targets --all-features → unchanged
86 passed, 0 failed, 1 ignored (no code touched in this commit).

Authored-By: claude <agents@vetcoders.io>
- src/wizard/services.rs: keep discovered services socket-less unless the source supplied a socket, so mux_gen owns ~/.config/mux/sockets assignment

- src/config.rs: add checked file read/copy helpers that reject parent traversal and canonicalize filesystem boundaries

- src/scan.rs and src/danger.rs: route config reads and backup copies through the checked helpers

Gate: pass

Tests: cargo fmt --check; cargo test; cargo clippy -- -D warnings; semgrep scan --config auto --error

Regressions: 0

Round-ID: marb-144039-5911-001
- src/wizard/persist.rs: filter rescanned sources through STEP 2 server selections for per-client and danger strategies

- src/danger.rs: add a scan-based danger planner so filtered services rewrite only selected entries while preserving unselected config blocks

- src/config.rs src/bin/rust-mux-proxy.rs src/runtime/tests.rs src/tray.rs: clear clippy and Semgrep gate debt without changing runtime mux behavior

Gate: pass

Tests: cargo fmt --check; cargo clippy --all-targets -- -D warnings; cargo test; semgrep scan --config auto --config p/rust .

Regressions: 0

Round-ID: marb-144039-5911-002
- src/mux_gen.rs: merge same-kind source scans before writing per-client files so Junie/Cursor/VSCode sources do not overwrite each other

Gate: pass

Tests: cargo test mux_gen::tests::per_client_outputs_merge_multiple_sources_for_same_kind --quiet; cargo fmt --check; cargo test --quiet; cargo clippy --all-targets -- -D warnings; semgrep scan --config auto --quiet

Regressions: 0

Round-ID: marb-144039-5911-003
- src/runtime/status.rs: move exported daemon status socket and status banner to rust-mux identity with a guard test

- src/multi_tui.rs: rebrand the multi-server dashboard header

- src/bin/rust-mux-proxy.rs: point proxy socket help at the generated mux socket directory

- src/wizard/ui.rs: make per-client summary filenames follow selected STEP 2 services instead of selected source rows

Gate: pass

Tests: cargo fmt --check; cargo clippy -- -D warnings; cargo test; semgrep scan --config .semgrep.yaml --error

Regressions: 0

Round-ID: marb-144039-5911-004

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a major refactor and rebranding of the project from rmcp-mux to rust-mux, including a new 5-step wizard flow for MCP client discovery and configuration. The changes include modularizing the runtime, implementing a safer 'danger' rewrite flow with backups, and adding tray dashboard support. My review identified several critical issues: the library entry point contains broken placeholders that will cause compilation errors, src/main.rs appears to be a duplicate file that should be removed, and the process scanning logic is brittle due to improper parsing of command lines and lack of terminal-width handling. Additionally, the configuration rewrite logic should use toml_edit to preserve user comments, and the tray daemon spawning should use current_exe() for robustness.

Comment thread src/lib.rs
Comment on lines +96 to +108
pub fn print_status_table(_status: &DaemonStatus) {
// Placeholder
}

pub async fn restart_single_service(_config: &Config, _name: &str) -> Result<()> {
// Placeholder
Ok(())
}

pub async fn status_all_servers(_config: &Config) -> Result<()> {
// Placeholder
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

These functions are currently implemented as empty placeholders, which breaks the functionality of the rust-mux binary. Furthermore, the binary in src/bin/rust-mux.rs attempts to import restart_single and status_all, but the placeholders here are named restart_single_service and status_all_servers. This will result in a compilation error.

Comment thread src/main.rs
Comment on lines +1 to +187
use std::path::PathBuf;

use anyhow::{anyhow, Result};
use clap::{Args, Parser, Subcommand};
use tracing_subscriber::filter::LevelFilter;

mod config;
mod danger;
mod mux_gen;
mod runtime;
mod scan;
mod state;
#[cfg(feature = "tray")]
mod tray;
mod wizard;

use crate::config::{expand_path, load_config, resolve_params};
use crate::runtime::{health_check, run_mux, run_proxy};
use crate::scan::{run_rewire_cmd, run_scan_cmd, run_status_cmd, RewireArgs, ScanArgs, StatusArgs};
use crate::wizard::WizardArgs;

/// Robust MCP mux: single MCP server child, many clients via UNIX socket,
/// initialize cache, ID rewriting, child restarts, and active client limit.
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct RootCli {
#[command(subcommand)]
command: Option<CliCommand>,
#[command(flatten)]
run: Cli,
}

#[derive(Subcommand, Debug)]
enum CliCommand {
/// Interactive wizard (ratatui) to build mux and host config snippets.
Wizard(WizardArgs),
/// Scan host configs and generate mux manifests/snippets.
Scan(ScanArgs),
/// Rewire a host config to point to rust-mux proxy.
Rewire(RewireArgs),
/// Proxy STDIO to a mux socket (for MCP hosts).
Proxy(ProxyArgs),
/// Check whether host configs are already pointed at the mux proxy.
Status(StatusArgs),
/// Simple health check: resolve config and try connecting to the mux socket.
Health(HealthArgs),
}

#[derive(Args, Debug, Clone)]
struct Cli {
/// Unix socket path for the mux listener. Can be overridden by config.
#[arg(long)]
socket: Option<PathBuf>,
/// MCP server command (e.g. `npx`). Can be overridden by config.
#[arg(long)]
cmd: Option<String>,
/// Arguments passed to the MCP server command.
#[arg(last = true)]
args: Vec<String>,
/// Max active clients (permits for concurrent server use).
#[arg(long, default_value = "5")]
max_active_clients: usize,
/// Lazy start MCP child only when first request arrives.
#[arg(long)]
lazy_start: Option<bool>,
/// Maximum request size in bytes before rejecting.
#[arg(long)]
max_request_bytes: Option<usize>,
/// Request timeout in milliseconds before the mux aborts pending calls.
#[arg(long)]
request_timeout_ms: Option<u64>,
/// Initial restart backoff in milliseconds.
#[arg(long)]
restart_backoff_ms: Option<u64>,
/// Maximum restart backoff in milliseconds.
#[arg(long)]
restart_backoff_max_ms: Option<u64>,
/// Maximum restarts before marking server failed (0 = unlimited).
#[arg(long)]
max_restarts: Option<u64>,
/// Log level (trace|debug|info|warn|error).
#[arg(long, default_value = "info")]
log_level: String,
/// Enable tray icon with live server status.
#[arg(long, default_value_t = false)]
tray: bool,
/// Service name shown in tray (defaults to socket file stem).
#[arg(long)]
service_name: Option<String>,
/// Optional config file (default ~/.codex/mcp.json)
#[arg(long)]
config: Option<PathBuf>,
/// Service key inside config (`servers.<name>`)
#[arg(long)]
service: Option<String>,
/// Optional path to write JSON status snapshots.
#[arg(long)]
status_file: Option<PathBuf>,
}

#[derive(Args, Debug, Clone)]
struct ProxyArgs {
/// Socket path to connect to.
#[arg(long)]
socket: PathBuf,
}

#[derive(Args, Debug, Clone)]
struct HealthArgs {
#[command(flatten)]
cli: Cli,
}

#[tokio::main]
async fn main() -> Result<()> {
let cli = RootCli::parse();

match &cli.command {
Some(CliCommand::Wizard(wargs)) => {
wizard::run_wizard(wargs.clone()).await?;
return Ok(());
}
Some(CliCommand::Scan(args)) => {
run_scan_cmd(args.clone())?;
return Ok(());
}
Some(CliCommand::Rewire(args)) => {
run_rewire_cmd(args.clone())?;
return Ok(());
}
Some(CliCommand::Proxy(args)) => {
return run_proxy(args.socket.clone()).await;
}
Some(CliCommand::Status(args)) => {
run_status_cmd(args.clone())?;
return Ok(());
}
Some(CliCommand::Health(args)) => {
run_health(args.cli.clone()).await?;
return Ok(());
}
None => {}
}

let cli = cli.run;

let config_path = cli
.config
.clone()
.unwrap_or_else(|| expand_path("~/.codex/mcp.json"));
let config = load_config(&config_path)?;

let params = resolve_params(&cli, config.as_ref())?;

let level = params
.log_level
.parse::<LevelFilter>()
.map_err(|_| anyhow!("invalid log level: {}", params.log_level))?;

tracing_subscriber::fmt()
.with_max_level(level)
.with_target(false)
.init();

tracing::info!(
service = params.service_name.as_str(),
socket = %params.socket.display(),
cmd = %params.cmd,
max_clients = params.max_clients,
tray = params.tray_enabled,
"mux starting"
);

run_mux(params).await
}

async fn run_health(cli: Cli) -> Result<()> {
let config_path = cli
.config
.clone()
.unwrap_or_else(|| expand_path("~/.codex/mcp.json"));
let config = load_config(&config_path)?;
let params = resolve_params(&cli, config.as_ref())?;
health_check(&params).await?;
println!("OK: connected to {}", params.socket.display());
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This file appears to be a duplicate of src/bin/rust-mux.rs but contains compilation errors (e.g., the run_mux call on line 174 is missing the required shutdown argument). Since Cargo.toml explicitly points to src/bin/rust-mux.rs as the binary source, this file should be removed to avoid confusion and potential build conflicts.

Comment thread src/wizard/services.rs

/// Extract command and arguments from a process command line
fn extract_cmd_and_args(args: &str) -> (String, Vec<String>) {
let parts: Vec<&str> = args.split_whitespace().collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using split_whitespace() to parse command lines from ps output is unreliable. It will incorrectly split paths or arguments that contain spaces (e.g., /Users/My Name/bin/server). This will result in broken configuration generation for many common environments.

References
  1. Avoid logic that fails on common filesystem paths (like those with spaces). (link)

Comment thread src/danger.rs
Comment on lines +570 to +645
fn rewrite_toml_keep_other_tables(
original: &str,
path: &Path,
kind: HostKind,
services: &[HostService],
proxy_cmd: &str,
proxy_args: &[String],
socket_dir: &Path,
) -> Result<(String, Vec<String>)> {
let mut root: toml::Value = toml::from_str(original)
.with_context(|| format!("failed to parse toml {}", path.display()))?;
let table = root
.as_table_mut()
.ok_or_else(|| anyhow!("{}: top-level TOML must be a table", path.display()))?;

let target_key = match kind {
HostKind::Codex => "mcp_servers",
_ => "mcp_servers",
};

let existing = table
.get(target_key)
.and_then(|v| v.as_table())
.cloned()
.unwrap_or_default();

let discovered_names: std::collections::HashSet<&str> =
services.iter().map(|s| s.name.as_str()).collect();
let mut new_table = toml::value::Table::new();
for (name, value) in &existing {
if !discovered_names.contains(name.as_str()) {
new_table.insert(name.clone(), value.clone());
}
}

let mut change_lines = Vec::with_capacity(services.len());
for svc in services {
let socket = svc.socket.clone().unwrap_or_else(|| {
socket_dir
.join(format!("{}.sock", svc.name))
.to_string_lossy()
.into_owned()
});
let mut args: Vec<String> = proxy_args.to_owned();
args.push("--socket".to_string());
args.push(socket);

let mut entry = toml::value::Table::new();
entry.insert("command".into(), toml::Value::String(proxy_cmd.to_string()));
entry.insert(
"args".into(),
toml::Value::Array(args.into_iter().map(toml::Value::String).collect()),
);
if let Some(env) = &svc.env {
let env_map: HashMap<String, toml::Value> = env
.iter()
.map(|(k, v)| (k.clone(), toml::Value::String(v.clone())))
.collect();
let mut env_table = toml::value::Table::new();
for (k, v) in env_map {
env_table.insert(k, v);
}
entry.insert("env".into(), toml::Value::Table(env_table));
}

change_lines.push(format!(
"rewrite `{}`: {} -> {}",
svc.name, svc.command, proxy_cmd
));
new_table.insert(svc.name.clone(), toml::Value::Table(entry));
}

table.insert(target_key.into(), toml::Value::Table(new_table));
let serialized = toml::to_string_pretty(&root).context("serialize rewritten toml")?;
Ok((serialized, change_lines))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The rewrite_toml_keep_other_tables function uses toml::Value, which does not preserve comments or original key ordering when serialized back to a string. For a 'danger' flow that modifies existing user configuration files, preserving comments is critical for user experience. Consider using the toml_edit crate for surgical updates that maintain the original file's structure.

Comment thread src/wizard/persist.rs Outdated
app.config_path.clone()
};

let mut cmd = Command::new("rust-mux");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Spawning the tray daemon using the hardcoded string "rust-mux" assumes the binary is already in the user's PATH. This may fail during development or in environments where the binary was just built. It is more robust to use std::env::current_exe() to ensure the same binary that is currently running the wizard is spawned.

Suggested change
let mut cmd = Command::new("rust-mux");
let mut cmd = Command::new(std::env::current_exe().unwrap_or_else(|_| PathBuf::from("rust-mux")));

Comment thread src/wizard/services.rs
let output = match Command::new("ps").args(["-eo", "pid,args"]).output() {
Ok(o) => o,
Err(_) => return detected,
Ok(o) if o.status.success() => o,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

On many Unix systems, ps output is truncated to the terminal width by default. This can lead to incomplete command strings being captured, which breaks the discovery heuristic. Consider adding the -ww flag to ensure the full command line is captured regardless of terminal width.

    let output = match Command::new("ps").args(["-eoww", "pid,args"]).output() {

Szowesgad added 4 commits May 6, 2026 16:30
- P2-02 + P3-01: append "Tray feature dependency risks" section to
  .vibecrafted/GUIDELINES.md (between Wizard/config doctrine and API
  surface). Documents the tray-icon → muda → gtk 0.18 → glib 0.18
  chain, RUSTSEC-2024-0429 unsoundness in glib 0.18.5 (not on
  rust-mux's call graph), 8 unmaintained GTK3 advisories, the
  --no-default-features CI mitigation, and the action item to track
  tray-icon for a glib >= 0.20 / gtk4-rs migration bump.
- P3-04: add CHANGELOG.md [0.4.1] - 2026-05-06 section above [0.4.0]
  capturing the 5-step wizard rebuild, three explicit strategies
  (Unified / Per-client / [DANGER] Auto-rewire), custom-path STEP 1
  input, tray daemon STEP 5 prompt, the discovery model flip
  (client-config-driven, ps-scan demoted to enrichment), the four
  marble fixes (self-skip dedup typo, per-client same-kind merge,
  STEP 2 selection honour, socket allocation ownership), the security
  audit result, and the +4 test coverage delta (83 → 87 passing).

Authored-By: claude <agents@vetcoders.io>
… tests

- 24 unit tests in src/wizard/keys.rs::tests covering every wizard step
  dispatcher: STEP 1 advance, STEP 2 zero-selection gate, STEP 3 strategy
  routing for Unified/PerClient/AutoRewire, STEP 4 Confirm/Cancel/Back
  branches per strategy, STEP 5 tray prompt, STEP 1 custom-path edit mode
  (i, char, Backspace, Enter empty, Enter populated, Esc), Up/Down clamping
  on STEP 1 and STEP 2, and q-quits-from-any-step.
- AppState fixture (make_app, make_app_with_sources, ok_source_at,
  make_service) sidesteps run_wizard's TTY requirement; advance_to_step2
  exercises the real scan_host_file path against tempdir-hosted JSON.
- draw_ui_renders_without_panic_on_every_step runs ratatui::TestBackend
  against draw_ui for every WizardStep with non-empty fixture state.
  Picked this over insta snapshots: same crash-regression coverage,
  no .snap fixtures, no review pipeline overhead.
- step1_n_advances_to_step2_when_a_source_is_selected asserts on the
  fixture-derived "memory" entry rather than total service count, since
  enrich_running_state legitimately injects DetectedRunning orphans
  from the host's live ps output during the test run.
- Closes vc-review P2-01 (wizard/{mod,keys,ui}.rs zero-test gap).
- Gates: cargo fmt --check, cargo clippy -D warnings,
  cargo test --all-targets --all-features all green
  (116 passed, 1 ignored; +24 in wizard::keys).

Authored-By: claude <agents@vetcoders.io>
- P3-02 (println! audit on stdio path): No offenders found.
  Audited src/lib.rs and src/runtime/status.rs; every println! site is on
  CLI subcommand paths (status_all, restart_single, print_status_table)
  invoked exclusively from src/bin/rust-mux.rs for --show-status,
  --restart-service, and the daemon-status subcommand. The stdio runtime
  proxy in src/runtime/proxy.rs uses tracing::{debug,warn} only and writes
  zero bytes to stdout outside the JSON-RPC frames. No code change needed.
- P3-03a (services.rs): drop #[allow(dead_code)] + "Why:" forward-reference
  comment from load_services_from_custom_path. The follow-up wiring noted
  in the comment never landed — keys.rs::add_custom_source rebuilt the
  same logic inline via scan_host_file + build_services_from_scans. Per
  Vibecrafter "no parallel systems" rule: deleted the orphan helper, its
  two inline tests, and the now-unused Path / scan_host_file imports.
- P3-03b (types.rs): drop the now-unconstructed ServiceSource::Custom
  variant and its match arms in ui.rs. Custom-path imports already flow
  through ServiceSource::Client { kind: HostKind::Custom, .. } via the
  scan-based path. Doc comment on ServiceSource explains the unification
  so a future agent does not re-introduce the parallel surface.
- P3-03c (ui.rs): delete fn health_marker (zero callers across src/);
  drop the dead-code-only HealthStatus import from the file-level use,
  and re-import it inside the test module where it is still used as a
  ServiceEntry literal.
- P3-05 (persist.rs::start_tray_daemon): prefer std::env::current_exe()
  with a sibling-binary lookup before falling back to PATH. Fixes the
  cargo-run workflow where `rust-mux` is not on $PATH. Existing
  start_tray_daemon_dry_run_short_circuits test still passes (the dry-run
  branch returns before spawn).

Gates: cargo fmt --check, cargo clippy -D warnings,
cargo test --all-targets --all-features all green
(116 lib passed, 1 ignored; +1 proxy bin test).

Authored-By: claude <agents@vetcoders.io>
Add first-class vibecrafted-mcp discovery via the local dev checkout or pip show, carry cwd through generated daemon config and runtime spawn, and add a non-interactive unified dry-run wizard smoke path.

Authored-By: Codex
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant