Skip to content

Commit 3c1942b

Browse files
committed
Merge PR #260: fix: batch fixes for issues #2485-#2496
2 parents 38937a8 + 2b22c57 commit 3c1942b

8 files changed

Lines changed: 508 additions & 51 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cortex-cli/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ zip = { workspace = true }
6666
flate2 = "1.0"
6767
tar = "0.4"
6868

69-
# For scrape command (HTML parsing)
69+
# For scrape command (HTML parsing and URL handling)
7070
scraper = "0.22"
71+
url = "2.5"
7172

7273
# For agent reference extraction from messages
7374
regex = { workspace = true }

cortex-cli/src/debug_cmd.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,10 @@ pub struct LspArgs {
723723
#[arg(long)]
724724
pub server: Option<String>,
725725

726+
/// Filter by programming language (e.g., python, rust, go).
727+
#[arg(long, short = 'l')]
728+
pub language: Option<String>,
729+
726730
/// Test LSP connection for a specific file.
727731
#[arg(long)]
728732
pub file: Option<PathBuf>,
@@ -807,6 +811,11 @@ async fn run_lsp(args: LspArgs) -> Result<()> {
807811
servers.retain(|s| s.name.to_lowercase().contains(&server_name.to_lowercase()));
808812
}
809813

814+
// Filter by language if specified
815+
if let Some(ref lang) = args.language {
816+
servers.retain(|s| s.language.to_lowercase().contains(&lang.to_lowercase()));
817+
}
818+
810819
// Connection test placeholder (actual implementation would require LSP client)
811820
let connection_test = if args.server.is_some() || args.file.is_some() {
812821
let server = args.server.as_deref().unwrap_or("auto-detect");
@@ -918,6 +927,10 @@ pub struct RipgrepArgs {
918927
#[arg(long)]
919928
pub dir: Option<PathBuf>,
920929

930+
/// Offer to install ripgrep if not found.
931+
#[arg(long)]
932+
pub install: bool,
933+
921934
/// Output as JSON.
922935
#[arg(long)]
923936
pub json: bool,
@@ -955,6 +968,123 @@ fn get_path_directories() -> Vec<PathBuf> {
955968
.unwrap_or_default()
956969
}
957970

971+
/// Attempt to install ripgrep using the system package manager.
972+
async fn install_ripgrep() -> Result<()> {
973+
#[cfg(target_os = "macos")]
974+
{
975+
// Try Homebrew first
976+
let output = tokio::process::Command::new("brew")
977+
.args(["install", "ripgrep"])
978+
.status()
979+
.await;
980+
981+
match output {
982+
Ok(status) if status.success() => {
983+
println!("ripgrep installed successfully via Homebrew!");
984+
return Ok(());
985+
}
986+
_ => {
987+
bail!("Failed to install via Homebrew. Is Homebrew installed?");
988+
}
989+
}
990+
}
991+
992+
#[cfg(target_os = "linux")]
993+
{
994+
// Try apt first (Debian/Ubuntu)
995+
if tokio::process::Command::new("which")
996+
.arg("apt")
997+
.output()
998+
.await
999+
.map(|o| o.status.success())
1000+
.unwrap_or(false)
1001+
{
1002+
let output = tokio::process::Command::new("sudo")
1003+
.args(["apt", "install", "-y", "ripgrep"])
1004+
.status()
1005+
.await;
1006+
1007+
match output {
1008+
Ok(status) if status.success() => {
1009+
println!("ripgrep installed successfully via apt!");
1010+
return Ok(());
1011+
}
1012+
_ => {}
1013+
}
1014+
}
1015+
1016+
// Try dnf (Fedora/RHEL)
1017+
if tokio::process::Command::new("which")
1018+
.arg("dnf")
1019+
.output()
1020+
.await
1021+
.map(|o| o.status.success())
1022+
.unwrap_or(false)
1023+
{
1024+
let output = tokio::process::Command::new("sudo")
1025+
.args(["dnf", "install", "-y", "ripgrep"])
1026+
.status()
1027+
.await;
1028+
1029+
match output {
1030+
Ok(status) if status.success() => {
1031+
println!("ripgrep installed successfully via dnf!");
1032+
return Ok(());
1033+
}
1034+
_ => {}
1035+
}
1036+
}
1037+
1038+
// Try pacman (Arch)
1039+
if tokio::process::Command::new("which")
1040+
.arg("pacman")
1041+
.output()
1042+
.await
1043+
.map(|o| o.status.success())
1044+
.unwrap_or(false)
1045+
{
1046+
let output = tokio::process::Command::new("sudo")
1047+
.args(["pacman", "-S", "--noconfirm", "ripgrep"])
1048+
.status()
1049+
.await;
1050+
1051+
match output {
1052+
Ok(status) if status.success() => {
1053+
println!("ripgrep installed successfully via pacman!");
1054+
return Ok(());
1055+
}
1056+
_ => {}
1057+
}
1058+
}
1059+
1060+
bail!("Could not detect a supported package manager (apt, dnf, pacman)");
1061+
}
1062+
1063+
#[cfg(target_os = "windows")]
1064+
{
1065+
// Try winget
1066+
let output = tokio::process::Command::new("winget")
1067+
.args(["install", "-e", "--id", "BurntSushi.ripgrep.MSVC"])
1068+
.status()
1069+
.await;
1070+
1071+
match output {
1072+
Ok(status) if status.success() => {
1073+
println!("ripgrep installed successfully via winget!");
1074+
return Ok(());
1075+
}
1076+
_ => {
1077+
bail!("Failed to install via winget. Is winget available?");
1078+
}
1079+
}
1080+
}
1081+
1082+
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1083+
{
1084+
bail!("Automatic installation not supported on this platform");
1085+
}
1086+
}
1087+
9581088
async fn run_ripgrep(args: RipgrepArgs) -> Result<()> {
9591089
let (available, path, version) = check_command_installed("rg").await;
9601090

@@ -1076,6 +1206,16 @@ async fn run_ripgrep(args: RipgrepArgs) -> Result<()> {
10761206
println!(" macOS: brew install ripgrep");
10771207
println!(" Linux: apt install ripgrep / dnf install ripgrep");
10781208
println!(" Windows: winget install BurntSushi.ripgrep.MSVC");
1209+
1210+
// Offer to install if --install flag was provided
1211+
if args.install {
1212+
println!();
1213+
println!("Attempting to install ripgrep...");
1214+
if let Err(e) = install_ripgrep().await {
1215+
eprintln!("Failed to install ripgrep: {e}");
1216+
eprintln!("Please install manually using the commands above.");
1217+
}
1218+
}
10791219
}
10801220
}
10811221

cortex-cli/src/main.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,11 @@ struct LogoutCommand {
531531
/// Completion command.
532532
#[derive(Args)]
533533
struct CompletionCommand {
534-
/// Shell to generate completions for
535-
#[arg(value_enum, default_value_t = Shell::Bash)]
536-
shell: Shell,
534+
/// Shell to generate completions for.
535+
/// If not specified, attempts to detect from $SHELL environment variable.
536+
/// Falls back to bash if detection fails.
537+
#[arg(value_enum)]
538+
shell: Option<Shell>,
537539
}
538540

539541
/// Resume command.
@@ -699,6 +701,17 @@ struct ServeCommand {
699701
#[arg(long = "auth-token")]
700702
auth_token: Option<String>,
701703

704+
/// Enable CORS (Cross-Origin Resource Sharing) for all origins.
705+
/// This allows web browsers to make requests from any domain.
706+
#[arg(long)]
707+
cors: bool,
708+
709+
/// Allowed CORS origin(s). Can be specified multiple times.
710+
/// Example: --cors-origin "https://myapp.com" --cors-origin "https://localhost:3000"
711+
/// If not specified but --cors is used, allows all origins (*).
712+
#[arg(long = "cors-origin", value_name = "ORIGIN")]
713+
cors_origins: Vec<String>,
714+
702715
/// Enable mDNS service discovery (advertise on local network)
703716
#[arg(long = "mdns", default_value_t = false)]
704717
mdns: bool,
@@ -899,7 +912,8 @@ async fn main() -> Result<()> {
899912
);
900913
}
901914
Some(Commands::Completion(completion_cli)) => {
902-
generate_completions(completion_cli.shell);
915+
let shell = completion_cli.shell.unwrap_or_else(detect_shell_from_env);
916+
generate_completions(shell);
903917
Ok(())
904918
}
905919
Some(Commands::Sandbox(sandbox_args)) => match sandbox_args.cmd {
@@ -1006,6 +1020,44 @@ async fn run_tui(initial_prompt: Option<String>, args: &InteractiveArgs) -> Resu
10061020
// - Session continuation (--session-id)
10071021
// - Timeout and max-turns controls
10081022

1023+
/// Detect the shell from the SHELL environment variable.
1024+
/// Falls back to Bash if detection fails.
1025+
fn detect_shell_from_env() -> Shell {
1026+
if let Ok(shell_path) = std::env::var("SHELL") {
1027+
let shell_name = std::path::Path::new(&shell_path)
1028+
.file_name()
1029+
.and_then(|n| n.to_str())
1030+
.unwrap_or("")
1031+
.to_lowercase();
1032+
1033+
match shell_name.as_str() {
1034+
"bash" => Shell::Bash,
1035+
"zsh" => Shell::Zsh,
1036+
"fish" => Shell::Fish,
1037+
"powershell" | "pwsh" => Shell::PowerShell,
1038+
"elvish" => Shell::Elvish,
1039+
_ => {
1040+
eprintln!(
1041+
"Warning: Unknown shell '{}' from $SHELL. Defaulting to bash.",
1042+
shell_name
1043+
);
1044+
Shell::Bash
1045+
}
1046+
}
1047+
} else {
1048+
// No SHELL env var, try to detect from other sources
1049+
#[cfg(windows)]
1050+
{
1051+
// On Windows, default to PowerShell
1052+
Shell::PowerShell
1053+
}
1054+
#[cfg(not(windows))]
1055+
{
1056+
Shell::Bash
1057+
}
1058+
}
1059+
}
1060+
10091061
fn generate_completions(shell: Shell) {
10101062
use std::io::Write;
10111063

@@ -1704,9 +1756,21 @@ async fn run_serve(serve_cli: ServeCommand) -> Result<()> {
17041756
cortex_app_server::config::AuthConfig::default()
17051757
};
17061758

1759+
// Configure CORS origins
1760+
let cors_origins = if !serve_cli.cors_origins.is_empty() {
1761+
serve_cli.cors_origins.clone()
1762+
} else if serve_cli.cors {
1763+
// --cors without specific origins means allow all (empty vec = permissive)
1764+
vec![]
1765+
} else {
1766+
// Default: no CORS (will use permissive by default in the server)
1767+
vec![]
1768+
};
1769+
17071770
let config = cortex_app_server::ServerConfig {
17081771
listen_addr: format!("{}:{}", serve_cli.host, serve_cli.port),
17091772
auth: auth_config,
1773+
cors_origins,
17101774
..Default::default()
17111775
};
17121776

@@ -1721,6 +1785,14 @@ async fn run_serve(serve_cli: ServeCommand) -> Result<()> {
17211785
serve_cli.host, serve_cli.port, auth_status
17221786
));
17231787

1788+
if serve_cli.cors || !serve_cli.cors_origins.is_empty() {
1789+
if serve_cli.cors_origins.is_empty() {
1790+
println!("CORS: Allowing all origins (*)");
1791+
} else {
1792+
println!("CORS: Allowing origins: {:?}", serve_cli.cors_origins);
1793+
}
1794+
}
1795+
17241796
// Setup mDNS advertising if enabled
17251797
let mut mdns_service = if serve_cli.mdns && !serve_cli.no_mdns {
17261798
let mut mdns = MdnsService::new();

0 commit comments

Comments
 (0)