Skip to main content

bctx_forge/substrate/
local.rs

1use super::{Substrate, SubstrateCommand, SubstrateError, SubstrateResult};
2use std::process::Command;
3use std::time::Instant;
4
5pub struct LocalSubstrate;
6
7impl Substrate for LocalSubstrate {
8    fn execute(&self, cmd: SubstrateCommand) -> Result<SubstrateResult, SubstrateError> {
9        let start = Instant::now();
10
11        let mut builder = Command::new(&cmd.program);
12        builder.args(&cmd.args);
13
14        if let Some(ref cwd) = cmd.working_dir {
15            builder.current_dir(cwd);
16        }
17
18        // Ensure standard PATH is available even when launched as an MCP server.
19        // Windows uses ';' as separator and has no /usr/local/bin — only augment on Unix.
20        let inherited_path = std::env::var("PATH").unwrap_or_default();
21        #[cfg(not(windows))]
22        {
23            let augmented = if inherited_path.is_empty() {
24                "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
25            } else {
26                format!("/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:{inherited_path}")
27            };
28            builder.env("PATH", augmented);
29        }
30        #[cfg(windows)]
31        if !inherited_path.is_empty() {
32            builder.env("PATH", &inherited_path);
33        }
34
35        for (k, v) in &cmd.env {
36            builder.env(k, v);
37        }
38
39        let output = builder.output().map_err(|e| {
40            if e.kind() == std::io::ErrorKind::NotFound {
41                SubstrateError::NotFound(cmd.program.clone())
42            } else {
43                SubstrateError::Io(e)
44            }
45        })?;
46
47        let duration_ms = start.elapsed().as_millis() as u64;
48
49        Ok(SubstrateResult {
50            stdout: output.stdout,
51            stderr: output.stderr,
52            exit_code: output.status.code().unwrap_or(-1),
53            duration_ms,
54        })
55    }
56
57    fn is_available(&self) -> bool {
58        true
59    }
60}