rust-mux 0.4.0: client-config discovery + 5-step wizard with strategy choice#1
rust-mux 0.4.0: client-config discovery + 5-step wizard with strategy choice#1Szowesgad wants to merge 20 commits into
Conversation
- 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
There was a problem hiding this comment.
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.
| 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(()) | ||
| } |
There was a problem hiding this comment.
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.
| 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(¶ms).await?; | ||
| println!("OK: connected to {}", params.socket.display()); | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
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.
|
|
||
| /// 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(); |
There was a problem hiding this comment.
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
- Avoid logic that fails on common filesystem paths (like those with spaces). (link)
| 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)) | ||
| } |
There was a problem hiding this comment.
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.
| app.config_path.clone() | ||
| }; | ||
|
|
||
| let mut cmd = Command::new("rust-mux"); |
There was a problem hiding this comment.
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.
| let mut cmd = Command::new("rust-mux"); | |
| let mut cmd = Command::new(std::env::current_exe().unwrap_or_else(|_| PathBuf::from("rust-mux"))); |
| let output = match Command::new("ps").args(["-eo", "pid,args"]).output() { | ||
| Ok(o) => o, | ||
| Err(_) => return detected, | ||
| Ok(o) if o.status.success() => o, |
There was a problem hiding this comment.
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() {- 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
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
scan::scan_hosts()+merge_services()drive STEP 1 and STEP 2default_sources()across Claude Code/Desktop, Codex, Junie (×3 paths), Gemini, plus legacy editor hostsito enter)Strategies (STEP 3)
~/.config/mux/{config.toml, mcp.json, mcp.toml}with dedup + per-client startup snippetsCONFIRMprompt and rollback commands; reuses the existingcrate::dangerplan/preview/execute machineryTray prompt (STEP 5)
Branding + canonical paths
Docs
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:
Each `Gate: pass; Regressions: 0`.
Quality gates
`prview --remote-only feat/update-and-modularize main --with-tests --with-lint`:
`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:
Test plan
/.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