Skip to content

Commit 699ee06

Browse files
committed
feat(cortex-cli): implement open PR features (#183)
This commit implements features from the following open PRs: PR #166 - CLI features and behavior improvements: - Add --yes/-y flag to logout command to skip confirmation prompt - Add color preview in 'agent show' command (displays hex color as ANSI block) - Update environment variable names from FABRIC_ to CORTEX_ prefix (FABRIC_ still supported for backward compatibility) PR #136 - MCP server configuration options: - Add --transport flag (stdio/http, default: stdio) - Add --port flag for HTTP transport (default: 3001) - Add --host flag for HTTP transport (default: 127.0.0.1) - Add --name flag for server name (default: cortex-mcp-server) - Add mcp-http feature flag for HTTP transport support Additional improvements: - Add cortex-mcp-server dependency to cortex-cli - Update debug command to show both CORTEX_ and FABRIC_ env vars - Improve documentation for environment variables
1 parent 6c8cccf commit 699ee06

File tree

7 files changed

+163
-24
lines changed

7 files changed

+163
-24
lines changed

Cargo.lock

Lines changed: 19 additions & 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ workspace = true
2020
default = ["cortex-tui"]
2121
# Use the new Cortex TUI (120 FPS, Cortex theme)
2222
cortex-tui = ["dep:cortex-tui"]
23+
# Enable HTTP transport for MCP server
24+
mcp-http = ["cortex-mcp-server/http"]
2325

2426
[dependencies]
2527
cortex-engine = { workspace = true }
@@ -31,6 +33,7 @@ cortex-login = { workspace = true }
3133
cortex-process-hardening = { workspace = true }
3234
cortex-app-server = { workspace = true }
3335
cortex-update = { workspace = true }
36+
cortex-mcp-server = { workspace = true }
3437

3538
clap = { workspace = true }
3639
clap_complete = { workspace = true }

cortex-cli/src/agent_cmd.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,9 @@ async fn run_show(args: ShowArgs) -> Result<()> {
839839
}
840840

841841
if let Some(ref color) = agent.color {
842-
println!("Color: {color}");
842+
// Display color with preview (colored block using ANSI escape codes)
843+
let color_preview = format_color_preview(color);
844+
println!("Color: {color} {color_preview}");
843845
}
844846

845847
if !agent.tags.is_empty() {
@@ -1512,6 +1514,30 @@ Provide architectural recommendations with:
15121514
- Implementation roadmap
15131515
"#;
15141516

1517+
/// Format a hex color as an ANSI-colored preview block.
1518+
///
1519+
/// Converts a hex color like "#FF5733" to an ANSI escape sequence that
1520+
/// displays a colored block (using true color if supported).
1521+
fn format_color_preview(hex_color: &str) -> String {
1522+
// Parse hex color (strip leading # if present)
1523+
let hex = hex_color.trim_start_matches('#');
1524+
1525+
// Only process valid 6-character hex colors
1526+
if hex.len() != 6 {
1527+
return String::new();
1528+
}
1529+
1530+
// Parse RGB components
1531+
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(128);
1532+
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(128);
1533+
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(128);
1534+
1535+
// Create ANSI true color (24-bit) escape sequence for background
1536+
// Format: \x1b[48;2;R;G;Bm (background color)
1537+
// Use 2 space characters as the "block" with reset at end
1538+
format!("\x1b[48;2;{};{};{}m \x1b[0m", r, g, b)
1539+
}
1540+
15151541
#[cfg(test)]
15161542
mod tests {
15171543
use super::*;

cortex-cli/src/completion_setup.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,7 @@ pub fn maybe_prompt_completion_setup() {
262262
println!();
263263
println!("Welcome to Cortex CLI!");
264264
println!();
265-
println!(
266-
"Would you like to enable tab completion for your shell ({shell_str})?",
267-
);
265+
println!("Would you like to enable tab completion for your shell ({shell_str})?",);
268266
println!("This will allow you to press TAB to complete commands and options.");
269267
println!();
270268
print!("Enable tab completion? [y/N] ");

cortex-cli/src/debug_cmd.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,28 @@ async fn run_config(args: ConfigArgs) -> Result<()> {
128128

129129
let environment = if args.env {
130130
let mut env_vars = HashMap::new();
131-
let fabric_vars = [
131+
// Cortex environment variables (CORTEX_ prefix is preferred, FABRIC_ supported for compat)
132+
let cortex_vars = [
133+
"CORTEX_HOME",
134+
"CORTEX_MODEL",
135+
"CORTEX_PROVIDER",
136+
"CORTEX_API_KEY",
137+
"CORTEX_DEBUG",
138+
"CORTEX_LOG_LEVEL",
139+
"CORTEX_AUTH_TOKEN",
140+
"CORTEX_API_URL",
141+
// Legacy FABRIC_ variables (backward compatibility)
132142
"FABRIC_HOME",
133143
"FABRIC_MODEL",
134144
"FABRIC_PROVIDER",
135-
"CORTEX_API_KEY",
136145
"FABRIC_DEBUG",
137146
"FABRIC_LOG_LEVEL",
138-
"CORTEX_AUTH_TOKEN",
139-
"CORTEX_API_URL",
147+
// Standard environment variables
140148
"EDITOR",
141149
"VISUAL",
142150
"SHELL",
143151
];
144-
for var in fabric_vars {
152+
for var in cortex_vars {
145153
if let Ok(val) = std::env::var(var) {
146154
// Mask API keys
147155
let display_val = if var.contains("API_KEY") || var.contains("SECRET") {

cortex-cli/src/login.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,20 @@ pub async fn run_login_status(config_overrides: CliConfigOverrides) -> ! {
139139
}
140140

141141
/// Run logout.
142-
pub async fn run_logout(config_overrides: CliConfigOverrides) -> ! {
142+
///
143+
/// # Arguments
144+
/// * `config_overrides` - CLI configuration overrides
145+
/// * `skip_confirmation` - If true, skip the confirmation prompt (--yes flag)
146+
pub async fn run_logout(config_overrides: CliConfigOverrides, skip_confirmation: bool) -> ! {
143147
check_duplicate_config_overrides(&config_overrides);
144148
let fabric_home = get_fabric_home();
145149

146150
// Check if user is logged in first
147151
match load_auth_with_fallback(&fabric_home) {
148152
Ok(Some(_)) => {
149153
// User is logged in, ask for confirmation if terminal is interactive
150-
if std::io::stdin().is_terminal() {
154+
// unless --yes flag is passed
155+
if !skip_confirmation && std::io::stdin().is_terminal() {
151156
eprint!(
152157
"Are you sure you want to log out? This will remove your stored credentials. [y/N]: "
153158
);

cortex-cli/src/main.rs

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use cortex_common::CliConfigOverrides;
3939
/// Log verbosity level for CLI output.
4040
///
4141
/// Controls the amount of logging detail shown. Can also be set via
42-
/// the FABRIC_LOG_LEVEL environment variable.
42+
/// the CORTEX_LOG_LEVEL environment variable.
4343
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
4444
pub enum LogLevel {
4545
/// Only show errors
@@ -176,7 +176,7 @@ struct InteractiveArgs {
176176
images: Vec<PathBuf>,
177177

178178
/// Set log verbosity level (error, warn, info, debug, trace)
179-
/// Can also be set via FABRIC_LOG_LEVEL environment variable
179+
/// Can also be set via CORTEX_LOG_LEVEL environment variable
180180
#[arg(long = "log-level", short = 'L', value_enum, default_value = "info")]
181181
log_level: LogLevel,
182182

@@ -211,8 +211,10 @@ enum Commands {
211211
/// Manage agents (list, create, show)
212212
Agent(AgentCli),
213213

214-
/// Run the MCP server (stdio transport)
215-
McpServer,
214+
/// Run the MCP server.
215+
/// Exposes Cortex tools via the Model Context Protocol for integration
216+
/// with other AI applications and IDEs.
217+
McpServer(McpServerCommand),
216218

217219
/// Generate shell completion scripts
218220
Completion(CompletionCommand),
@@ -350,6 +352,11 @@ enum LoginSubcommand {
350352
struct LogoutCommand {
351353
#[clap(skip)]
352354
config_overrides: CliConfigOverrides,
355+
356+
/// Skip confirmation prompt and log out immediately.
357+
/// Useful for scripting and automation.
358+
#[arg(short = 'y', long = "yes")]
359+
yes: bool,
353360
}
354361

355362
/// Completion command.
@@ -368,6 +375,28 @@ struct ManCommand {
368375
output: Option<PathBuf>,
369376
}
370377

378+
/// MCP Server command.
379+
#[derive(Args)]
380+
struct McpServerCommand {
381+
/// Transport to use for MCP communication.
382+
/// - stdio: Standard input/output (default, used by Claude Desktop, VS Code, etc.)
383+
/// - http: HTTP JSON-RPC server (for web clients)
384+
#[arg(long, default_value = "stdio")]
385+
transport: String,
386+
387+
/// Port for HTTP transport (ignored for stdio).
388+
#[arg(long, default_value_t = 3001)]
389+
port: u16,
390+
391+
/// Host to bind for HTTP transport (ignored for stdio).
392+
#[arg(long, default_value = "127.0.0.1")]
393+
host: String,
394+
395+
/// Server name advertised to MCP clients.
396+
#[arg(long, default_value = "cortex-mcp-server")]
397+
name: String,
398+
}
399+
371400
/// Resume command.
372401
#[derive(Args)]
373402
struct ResumeCommand {
@@ -509,12 +538,15 @@ async fn main() -> Result<()> {
509538
// Initialize logging only for non-TUI commands
510539
// TUI mode has its own file-based logging to avoid stdout pollution
511540
if cli.command.is_some() {
512-
// Determine log level with precedence: --debug flag > --log-level > FABRIC_LOG_LEVEL env > default (info)
541+
// Determine log level with precedence: --debug flag > --log-level > CORTEX_LOG_LEVEL env > default (info)
542+
// Note: FABRIC_LOG_LEVEL is still supported for backward compatibility
513543
let log_level = if cli.interactive.debug {
514544
// Deprecated --debug flag takes precedence for backward compatibility
515545
LogLevel::Debug
516-
} else if let Ok(env_level) = std::env::var("FABRIC_LOG_LEVEL") {
517-
// Check environment variable
546+
} else if let Ok(env_level) =
547+
std::env::var("CORTEX_LOG_LEVEL").or_else(|_| std::env::var("FABRIC_LOG_LEVEL"))
548+
{
549+
// Check environment variable (CORTEX_LOG_LEVEL preferred, FABRIC_LOG_LEVEL for compat)
518550
LogLevel::from_str_loose(&env_level).unwrap_or(cli.interactive.log_level)
519551
} else {
520552
cli.interactive.log_level
@@ -578,16 +610,12 @@ async fn main() -> Result<()> {
578610
Ok(())
579611
}
580612
Some(Commands::Logout(logout_cli)) => {
581-
run_logout(logout_cli.config_overrides).await;
613+
run_logout(logout_cli.config_overrides, logout_cli.yes).await;
582614
Ok(())
583615
}
584616
Some(Commands::Mcp(mcp_cli)) => mcp_cli.run().await,
585617
Some(Commands::Agent(agent_cli)) => agent_cli.run().await,
586-
Some(Commands::McpServer) => {
587-
bail!(
588-
"MCP server mode is not yet implemented. Use 'cortex mcp' for MCP server management."
589-
);
590-
}
618+
Some(Commands::McpServer(mcp_server_cli)) => run_mcp_server(mcp_server_cli).await,
591619
Some(Commands::Completion(completion_cli)) => {
592620
generate_completions(completion_cli.shell);
593621
Ok(())
@@ -1333,6 +1361,58 @@ async fn run_serve(serve_cli: ServeCommand) -> Result<()> {
13331361
result.map_err(Into::into)
13341362
}
13351363

1364+
/// Run the MCP server with the specified configuration.
1365+
async fn run_mcp_server(mcp_server_cli: McpServerCommand) -> Result<()> {
1366+
use cortex_mcp_server::McpServerBuilder;
1367+
1368+
eprintln!("Starting Cortex MCP server...");
1369+
eprintln!(" Transport: {}", mcp_server_cli.transport);
1370+
eprintln!(" Name: {}", mcp_server_cli.name);
1371+
1372+
// Build the MCP server
1373+
let version = env!("CARGO_PKG_VERSION");
1374+
let server = McpServerBuilder::new(&mcp_server_cli.name, version).build()?;
1375+
1376+
match mcp_server_cli.transport.to_lowercase().as_str() {
1377+
"stdio" => {
1378+
eprintln!(" Mode: stdio (reading from stdin, writing to stdout)");
1379+
eprintln!();
1380+
// Run stdio transport
1381+
server.run_stdio().await?;
1382+
}
1383+
"http" => {
1384+
// HTTP transport requires the "http" feature in cortex-mcp-server
1385+
#[cfg(feature = "mcp-http")]
1386+
{
1387+
let addr: std::net::SocketAddr =
1388+
format!("{}:{}", mcp_server_cli.host, mcp_server_cli.port)
1389+
.parse()
1390+
.context("Invalid host:port address")?;
1391+
eprintln!(" Listening: http://{}", addr);
1392+
eprintln!();
1393+
// Run HTTP transport
1394+
server.run_http(addr).await?;
1395+
}
1396+
#[cfg(not(feature = "mcp-http"))]
1397+
{
1398+
bail!(
1399+
"HTTP transport is not available. The MCP server was built without HTTP support.\n\
1400+
Use --transport stdio (default) for stdio transport, which is compatible with\n\
1401+
Claude Desktop, VS Code MCP extensions, and other MCP clients."
1402+
);
1403+
}
1404+
}
1405+
other => {
1406+
bail!(
1407+
"Unknown transport '{}'. Supported transports: stdio, http",
1408+
other
1409+
);
1410+
}
1411+
}
1412+
1413+
Ok(())
1414+
}
1415+
13361416
async fn run_servers(servers_cli: ServersCommand) -> Result<()> {
13371417
use cortex_engine::MdnsBrowser;
13381418
use std::time::Duration;

0 commit comments

Comments
 (0)