Skip to content

Commit 61f3be3

Browse files
echobtfactorydroid
andauthored
refactor(cortex-cli): centralize utilities into modular structure (#465)
This refactoring introduces a new utils module hierarchy that centralizes shared functionality across the CLI commands: - utils/mod.rs: Module exports and organization - utils/clipboard.rs: Cross-platform clipboard read/write operations - utils/paths.rs: Path manipulation, validation, and security checks - utils/session.rs: Unified session ID resolution (UUID and short IDs) - utils/terminal.rs: Terminal color detection, formatting, ANSI codes - utils/validation.rs: URL, server name, env var, and model validation Benefits: - DRY: Eliminates duplicated validation logic across mcp_cmd, run_cmd, etc. - Type safety: SessionIdError enum for better error handling - Security: Centralized path traversal and URL validation - Modularity: Clean separation of concerns for easier maintenance This is the first phase of the cortex-cli refactoring effort. Future work will update existing commands to use these shared utilities. Co-authored-by: Droid Agent <droid@factory.ai>
1 parent 53f158b commit 61f3be3

9 files changed

Lines changed: 1236 additions & 0 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ serde = { workspace = true }
6363
ctor = "0.5"
6464
base64 = { workspace = true }
6565

66+
# For error types in utilities
67+
thiserror = { workspace = true }
68+
6669
# For upgrade command (HTTP requests) and scrape command (cookie support)
6770
reqwest = { workspace = true, features = ["json", "cookies"] }
6871

cortex-cli/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,22 @@
1818
//! - WSL path handling
1919
//! - Terminal cleanup for graceful shutdown
2020
//! - SIGTERM handling for graceful process termination
21+
//!
22+
//! # Module Organization
23+
//!
24+
//! The crate is organized into the following structure:
25+
//!
26+
//! - `utils/` - Shared utilities (validation, paths, clipboard, terminal)
27+
//! - Command modules - Individual CLI commands (`*_cmd.rs`)
28+
//! - `styled_output` - Themed terminal output formatting
29+
//! - `login` - Authentication management
2130
2231
use std::panic;
2332
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
2433

34+
// Re-export the utilities module for shared functionality
35+
pub mod utils;
36+
2537
static CLEANUP_REGISTERED: AtomicBool = AtomicBool::new(false);
2638
static PANIC_HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
2739

cortex-cli/src/utils/clipboard.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! Clipboard operations for the Cortex CLI.
2+
//!
3+
//! Provides cross-platform clipboard read/write functionality.
4+
5+
use anyhow::{Context, Result, bail};
6+
use std::io::Write;
7+
use std::process::{Command, Stdio};
8+
9+
/// Copy text to the system clipboard.
10+
///
11+
/// Uses platform-specific commands:
12+
/// - macOS: `pbcopy`
13+
/// - Linux: `xclip` or `xsel` (with X11 fallback)
14+
/// - Windows: `clip.exe`
15+
///
16+
/// # Arguments
17+
/// * `text` - The text to copy to clipboard
18+
///
19+
/// # Returns
20+
/// `Ok(())` on success, or an error if clipboard access failed.
21+
///
22+
/// # Example
23+
/// ```ignore
24+
/// copy_to_clipboard("Hello, world!")?;
25+
/// ```
26+
pub fn copy_to_clipboard(text: &str) -> Result<()> {
27+
#[cfg(target_os = "macos")]
28+
{
29+
let mut child = Command::new("pbcopy")
30+
.stdin(Stdio::piped())
31+
.spawn()
32+
.context("Failed to spawn pbcopy")?;
33+
34+
if let Some(ref mut stdin) = child.stdin {
35+
stdin.write_all(text.as_bytes())?;
36+
}
37+
child.wait()?;
38+
Ok(())
39+
}
40+
41+
#[cfg(target_os = "linux")]
42+
{
43+
// Try xclip first, then xsel as fallback
44+
let result = Command::new("xclip")
45+
.args(["-selection", "clipboard"])
46+
.stdin(Stdio::piped())
47+
.spawn();
48+
49+
let mut child = match result {
50+
Ok(c) => c,
51+
Err(_) => Command::new("xsel")
52+
.args(["--clipboard", "--input"])
53+
.stdin(Stdio::piped())
54+
.spawn()
55+
.context("Failed to spawn clipboard command (tried xclip and xsel)")?,
56+
};
57+
58+
if let Some(ref mut stdin) = child.stdin {
59+
stdin.write_all(text.as_bytes())?;
60+
}
61+
child.wait()?;
62+
Ok(())
63+
}
64+
65+
#[cfg(target_os = "windows")]
66+
{
67+
let mut child = Command::new("clip")
68+
.stdin(Stdio::piped())
69+
.spawn()
70+
.context("Failed to spawn clip.exe")?;
71+
72+
if let Some(ref mut stdin) = child.stdin {
73+
stdin.write_all(text.as_bytes())?;
74+
}
75+
child.wait()?;
76+
Ok(())
77+
}
78+
79+
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
80+
{
81+
bail!("Clipboard not supported on this platform")
82+
}
83+
}
84+
85+
/// Read text from the system clipboard.
86+
///
87+
/// Uses platform-specific commands:
88+
/// - macOS: `pbpaste`
89+
/// - Linux: `xclip` or `xsel` (with X11 fallback)
90+
/// - Windows: `Get-Clipboard` via PowerShell
91+
///
92+
/// # Returns
93+
/// The clipboard content as a string, or an error if clipboard access failed.
94+
///
95+
/// # Example
96+
/// ```ignore
97+
/// let content = read_clipboard()?;
98+
/// println!("Clipboard: {}", content);
99+
/// ```
100+
pub fn read_clipboard() -> Result<String> {
101+
#[cfg(target_os = "macos")]
102+
{
103+
let output = Command::new("pbpaste").output()?;
104+
if output.status.success() {
105+
Ok(String::from_utf8_lossy(&output.stdout).to_string())
106+
} else {
107+
bail!("Failed to read clipboard")
108+
}
109+
}
110+
111+
#[cfg(target_os = "linux")]
112+
{
113+
// Try xclip first, then xsel
114+
let output = Command::new("xclip")
115+
.args(["-selection", "clipboard", "-o"])
116+
.output()
117+
.or_else(|_| {
118+
Command::new("xsel")
119+
.args(["--clipboard", "--output"])
120+
.output()
121+
})?;
122+
123+
if output.status.success() {
124+
Ok(String::from_utf8_lossy(&output.stdout).to_string())
125+
} else {
126+
bail!("Failed to read clipboard (tried xclip and xsel)")
127+
}
128+
}
129+
130+
#[cfg(target_os = "windows")]
131+
{
132+
let output = Command::new("powershell")
133+
.args(["-Command", "Get-Clipboard"])
134+
.output()?;
135+
136+
if output.status.success() {
137+
Ok(String::from_utf8_lossy(&output.stdout).to_string())
138+
} else {
139+
bail!("Failed to read clipboard")
140+
}
141+
}
142+
143+
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
144+
{
145+
bail!("Clipboard not supported on this platform")
146+
}
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
// Clipboard tests are difficult to run in CI environments
152+
// as they require a display server or clipboard service
153+
}

cortex-cli/src/utils/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//! Shared utilities for the Cortex CLI.
2+
//!
3+
//! This module centralizes common functionality used across multiple CLI commands:
4+
//! - Session ID resolution and validation
5+
//! - URL validation and sanitization
6+
//! - File validation and security checks
7+
//! - Clipboard operations
8+
//! - Path validation utilities
9+
//! - Terminal color detection
10+
11+
pub mod clipboard;
12+
pub mod paths;
13+
pub mod session;
14+
pub mod terminal;
15+
pub mod validation;
16+
17+
pub use clipboard::{copy_to_clipboard, read_clipboard};
18+
pub use paths::{expand_tilde, get_cortex_home, validate_path_safety};
19+
pub use session::{SessionIdError, resolve_session_id};
20+
pub use terminal::{TermColor, is_terminal_output, should_use_colors};
21+
pub use validation::{
22+
validate_env_var_name, validate_env_var_value, validate_model_name, validate_server_name,
23+
validate_url, validate_url_allowing_local,
24+
};

0 commit comments

Comments
 (0)