use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug, Clone)]
#[command(
name = "acp-ws-bridge",
about = "WebSocket bridge for GitHub Copilot CLI ACP"
)]
pub struct Config {
#[arg(long, default_value = "8765")]
pub ws_port: u16,
#[arg(long, default_value = "3000")]
pub copilot_port: u16,
#[arg(long, default_value = "127.0.0.1")]
pub copilot_host: String,
#[arg(long, default_value = "0.0.0.0")]
pub listen_addr: String,
#[arg(long)]
pub tls_cert: Option<String>,
#[arg(long)]
pub tls_key: Option<String>,
#[arg(long, default_value = "604800")]
pub idle_timeout_secs: u64,
#[arg(long)]
pub generate_cert: bool,
#[arg(long, default_value = "localhost,127.0.0.1")]
pub cert_hostnames: String,
#[arg(long)]
pub api_port: Option<u16>,
#[arg(long, default_value = "info")]
pub log_level: String,
#[arg(long, default_value = "copilot")]
pub copilot_path: String,
#[arg(long, visible_alias = "command")]
pub acp_command: Option<String>,
#[arg(long, default_value = "true")]
pub spawn_copilot: bool,
#[arg(long)]
pub copilot_args: Vec<String>,
#[arg(long)]
pub copilot_mode: Option<String>,
#[arg(long)]
pub copilot_dir: Option<PathBuf>,
}
impl Config {
pub fn effective_copilot_mode(&self) -> &str {
if let Some(ref mode) = self.copilot_mode {
mode.as_str()
} else {
"stdio"
}
}
pub fn effective_copilot_dir(&self) -> anyhow::Result<PathBuf> {
match &self.copilot_dir {
Some(dir) => Ok(dir.clone()),
None => crate::paths::default_copilot_dir(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
use std::path::Path;
#[test]
fn test_default_config() {
let config = Config::parse_from(["test"]);
assert_eq!(config.ws_port, 8765);
assert_eq!(config.copilot_port, 3000);
assert_eq!(config.copilot_host, "127.0.0.1");
assert_eq!(config.listen_addr, "0.0.0.0");
assert_eq!(config.idle_timeout_secs, 604800);
assert!(config.tls_cert.is_none());
assert!(config.tls_key.is_none());
assert!(config.api_port.is_none());
assert!(!config.generate_cert);
assert_eq!(config.cert_hostnames, "localhost,127.0.0.1");
assert_eq!(config.log_level, "info");
assert_eq!(config.copilot_path, "copilot");
assert!(config.acp_command.is_none());
assert!(config.spawn_copilot);
assert!(config.copilot_args.is_empty());
assert!(config.copilot_mode.is_none());
assert!(config.copilot_dir.is_none());
assert_eq!(config.effective_copilot_mode(), "stdio");
assert!(config
.effective_copilot_dir()
.unwrap()
.ends_with(".copilot"));
}
#[test]
fn test_custom_config() {
let config = Config::parse_from([
"test",
"--ws-port",
"9999",
"--copilot-port",
"4000",
"--copilot-host",
"192.168.1.1",
"--listen-addr",
"127.0.0.1",
"--idle-timeout-secs",
"3600",
"--tls-cert",
"/tmp/cert.pem",
"--tls-key",
"/tmp/key.pem",
"--log-level",
"debug",
]);
assert_eq!(config.ws_port, 9999);
assert_eq!(config.copilot_port, 4000);
assert_eq!(config.copilot_host, "192.168.1.1");
assert_eq!(config.listen_addr, "127.0.0.1");
assert_eq!(config.idle_timeout_secs, 3600);
assert_eq!(config.tls_cert.as_deref(), Some("/tmp/cert.pem"));
assert_eq!(config.tls_key.as_deref(), Some("/tmp/key.pem"));
assert_eq!(config.log_level, "debug");
}
#[test]
fn test_acp_command_alias() {
let config = Config::parse_from([
"test",
"--command",
"copilot --acp --stdio --allow-all-tools",
]);
assert_eq!(
config.acp_command.as_deref(),
Some("copilot --acp --stdio --allow-all-tools")
);
}
#[test]
fn test_copilot_dir_custom() {
let config = Config::parse_from(["test", "--copilot-dir", "/tmp/copilot-data"]);
assert_eq!(
config.copilot_dir.as_deref(),
Some(Path::new("/tmp/copilot-data"))
);
assert_eq!(
config.effective_copilot_dir().unwrap(),
PathBuf::from("/tmp/copilot-data")
);
}
#[test]
fn test_api_port_default() {
let config = Config::parse_from(["test"]);
assert!(config.api_port.is_none());
}
#[test]
fn test_api_port_explicit() {
let config = Config::parse_from(["test", "--api-port", "9000"]);
assert_eq!(config.api_port, Some(9000));
}
#[test]
fn test_generate_cert_flag() {
let config = Config::parse_from(["test", "--generate-cert"]);
assert!(config.generate_cert);
}
#[test]
fn test_cert_hostnames_custom() {
let config = Config::parse_from(["test", "--cert-hostnames", "example.com,10.0.0.1"]);
assert_eq!(config.cert_hostnames, "example.com,10.0.0.1");
}
}