huskies: merge 554_story_multi_project_gateway_that_proxies_mcp_calls_to_per_project_docker_containers
This commit is contained in:
+47
-1
@@ -13,6 +13,7 @@ pub mod crdt_state;
|
||||
pub mod crdt_sync;
|
||||
pub mod crdt_wire;
|
||||
mod db;
|
||||
pub mod gateway;
|
||||
mod http;
|
||||
mod io;
|
||||
mod llm;
|
||||
@@ -53,6 +54,8 @@ struct CliArgs {
|
||||
agent: bool,
|
||||
/// Rendezvous WebSocket URL for agent mode (e.g. `ws://host:3001/crdt-sync`).
|
||||
rendezvous: Option<String>,
|
||||
/// Whether `--gateway` mode was requested (proxy MCP calls to per-project containers).
|
||||
gateway: bool,
|
||||
}
|
||||
|
||||
/// Parse CLI arguments into `CliArgs`, or exit early for `--help` / `--version`.
|
||||
@@ -61,6 +64,7 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
|
||||
let mut path: Option<String> = None;
|
||||
let mut init = false;
|
||||
let mut agent = false;
|
||||
let mut gateway = false;
|
||||
let mut rendezvous: Option<String> = None;
|
||||
let mut i = 0;
|
||||
|
||||
@@ -102,6 +106,9 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
|
||||
let val = &a["--rendezvous=".len()..];
|
||||
rendezvous = Some(val.to_string());
|
||||
}
|
||||
"--gateway" => {
|
||||
gateway = true;
|
||||
}
|
||||
"init" => {
|
||||
init = true;
|
||||
}
|
||||
@@ -125,13 +132,14 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
|
||||
return Err("agent mode requires --rendezvous <URL>".to_string());
|
||||
}
|
||||
|
||||
Ok(CliArgs { port, path, init, agent, rendezvous })
|
||||
Ok(CliArgs { port, path, init, agent, rendezvous, gateway })
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!("huskies [OPTIONS] [PATH]");
|
||||
println!("huskies init [OPTIONS] [PATH]");
|
||||
println!("huskies agent --rendezvous <URL> [OPTIONS] [PATH]");
|
||||
println!("huskies --gateway [OPTIONS] [PATH]");
|
||||
println!();
|
||||
println!("Serve a huskies project.");
|
||||
println!();
|
||||
@@ -151,6 +159,8 @@ fn print_help() {
|
||||
println!(" --port <PORT> Port to listen on (default: 3001). Persisted to project.toml.");
|
||||
println!(" --rendezvous <URL> WebSocket URL of the rendezvous peer (agent mode only).");
|
||||
println!(" Example: ws://server:3001/crdt-sync");
|
||||
println!(" --gateway Start in gateway mode. Reads projects.toml from PATH");
|
||||
println!(" (or cwd) and proxies MCP calls to per-project containers.");
|
||||
}
|
||||
|
||||
/// Resolve the optional positional path argument into an absolute `PathBuf`.
|
||||
@@ -206,6 +216,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
|
||||
let is_init = cli.init;
|
||||
let is_agent = cli.agent;
|
||||
let is_gateway = cli.gateway;
|
||||
let agent_rendezvous = cli.rendezvous.clone();
|
||||
let explicit_path = resolve_path_arg(cli.path.as_deref(), &cwd);
|
||||
|
||||
@@ -227,6 +238,17 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gateway mode: multi-project proxy ──────────────────────────────
|
||||
//
|
||||
// When `huskies --gateway` is invoked, skip the normal single-project
|
||||
// server and instead start a lightweight proxy that routes MCP calls
|
||||
// to per-project Docker containers based on a projects.toml config.
|
||||
if is_gateway {
|
||||
let config_dir = explicit_path.unwrap_or_else(|| cwd.clone());
|
||||
let config_path = config_dir.join("projects.toml");
|
||||
return gateway::run(&config_path, port).await;
|
||||
}
|
||||
|
||||
if is_init {
|
||||
// `huskies init [PATH]` — always scaffold, never search parents.
|
||||
let init_root = explicit_path.unwrap_or_else(|| cwd.clone());
|
||||
@@ -949,9 +971,33 @@ name = "coder"
|
||||
assert_eq!(result.path, None);
|
||||
assert!(!result.init);
|
||||
assert!(!result.agent);
|
||||
assert!(!result.gateway);
|
||||
assert_eq!(result.rendezvous, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_gateway_flag() {
|
||||
let args = vec!["--gateway".to_string()];
|
||||
let result = parse_cli_args(&args).unwrap();
|
||||
assert!(result.gateway);
|
||||
assert!(!result.init);
|
||||
assert!(!result.agent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_gateway_with_port_and_path() {
|
||||
let args = vec![
|
||||
"--gateway".to_string(),
|
||||
"--port".to_string(),
|
||||
"5000".to_string(),
|
||||
"/my/config".to_string(),
|
||||
];
|
||||
let result = parse_cli_args(&args).unwrap();
|
||||
assert!(result.gateway);
|
||||
assert_eq!(result.port, Some(5000));
|
||||
assert_eq!(result.path, Some("/my/config".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_unknown_flag_is_error() {
|
||||
let args = vec!["--serve".to_string()];
|
||||
|
||||
Reference in New Issue
Block a user