use std::io::Write;
use std::process::{Child, Command, Stdio};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use crate::pipe::cli::cli_builder;
use crate::transport::SpawnOptions;
use crate::core::types::CliTool;
#[derive(Debug, Clone, Default)]
pub struct ClaudeOptions {
pub append_system_prompt: Option<String>,
pub resume_session_id: Option<String>,
pub model: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct PipeProcessOptions {
pub extra_args: Vec<String>,
pub claude: ClaudeOptions,
}
pub struct PipeProcess {
child: Child,
stdin: Option<std::process::ChildStdin>,
output_rx: Receiver<String>,
tool: CliTool,
}
impl PipeProcess {
pub fn new(
tool: CliTool,
working_dir: &std::path::Path,
initial_prompt: &str,
) -> Result<Self, std::io::Error> {
Self::new_with_options(
tool,
working_dir,
initial_prompt,
PipeProcessOptions::default(),
)
}
pub fn new_with_options(
tool: CliTool,
working_dir: &std::path::Path,
initial_prompt: &str,
options: PipeProcessOptions,
) -> Result<Self, std::io::Error> {
let spawn_opts = Self::pipe_options_to_spawn_opts(tool, initial_prompt, &options, working_dir);
let mut cmd = Self::build_command_with_options(tool, &spawn_opts);
cmd.current_dir(working_dir);
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::null());
let mut child = cmd.spawn()?;
let stdin = child.stdin.take();
if let Some(mut s) = stdin {
if tool == CliTool::ClaudeCode {
s.write_all(initial_prompt.as_bytes())?;
s.flush()?;
}
drop(s); }
let stdin: Option<std::process::ChildStdin> = None;
let stdout = child
.stdout
.take()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "no stdout"))?;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
Self::reader_thread(stdout, tx);
});
Ok(Self {
child,
stdin,
output_rx: rx,
tool,
})
}
fn pipe_options_to_spawn_opts(
_tool: CliTool,
prompt: &str,
options: &PipeProcessOptions,
working_dir: &std::path::Path,
) -> SpawnOptions {
SpawnOptions {
working_dir: working_dir.to_path_buf(),
prompt: prompt.to_string(),
resume_session_id: options.claude.resume_session_id.clone(),
model: options.claude.model.clone(),
append_system_prompt: options.claude.append_system_prompt.clone(),
extra_args: options.extra_args.clone(),
env_vars: Vec::new(),
..Default::default()
}
}
fn build_command_with_options(tool: CliTool, opts: &SpawnOptions) -> Command {
let builder = cli_builder(tool);
let inner_cmd = builder.build_command(opts);
if cfg!(windows) {
let program = inner_cmd.get_program().to_string_lossy().to_string();
if program.ends_with(".cmd") || program.ends_with(".exe") {
let mut cmd = Command::new("cmd");
cmd.arg("/C");
cmd.arg(&program);
for arg in inner_cmd.get_args() {
cmd.arg(arg);
}
cmd
} else {
let cmd_name = format!("{}.cmd", program);
let has_cmd = std::env::var_os("PATH")
.map(|path| {
std::env::split_paths(&path)
.any(|dir| dir.join(&cmd_name).is_file())
})
.unwrap_or(false);
if has_cmd {
let mut cmd = Command::new("cmd");
cmd.arg("/C");
cmd.arg(&cmd_name);
for arg in inner_cmd.get_args() {
cmd.arg(arg);
}
cmd
} else {
let mut cmd = Command::new("bash");
cmd.arg("-c");
let mut shell_str = Self::shell_quote(&program);
for arg in inner_cmd.get_args() {
let s = arg.to_string_lossy();
shell_str.push(' ');
shell_str.push_str(&Self::shell_quote(&s));
}
cmd.arg(&shell_str);
cmd
}
}
} else {
inner_cmd
}
}
fn shell_quote(s: &str) -> String {
if s.is_empty() {
return "''".to_string();
}
if s.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '/') {
return s.to_string();
}
format!("'{}'", s.replace('\'', "'\\''"))
}
fn reader_thread(stdout: std::process::ChildStdout, tx: Sender<String>) {
use std::io::{BufRead, BufReader};
let reader = BufReader::new(stdout);
for line in reader.lines() {
match line {
Ok(line) => {
if tx.send(format!("{}\n", line)).is_err() {
break;
}
}
Err(_) => break,
}
}
}
pub fn try_recv(&self) -> Option<String> {
self.output_rx.try_recv().ok()
}
pub fn write(&mut self, data: &str) -> Result<(), std::io::Error> {
if let Some(stdin) = &mut self.stdin {
stdin.write_all(data.as_bytes())?;
stdin.flush()?;
}
Ok(())
}
pub fn is_running(&mut self) -> bool {
self.child.try_wait().ok().flatten().is_none()
}
pub fn kill(&mut self) -> Result<(), std::io::Error> {
self.child.kill()
}
pub fn wait(&mut self) -> Result<Option<std::process::ExitStatus>, std::io::Error> {
self.child.try_wait()
}
pub fn tool(&self) -> CliTool {
self.tool
}
}