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        let inherited_path = std::env::var("PATH").unwrap_or_default();
20        let augmented = if inherited_path.is_empty() {
21            "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
22        } else {
23            format!("/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:{inherited_path}")
24        };
25        builder.env("PATH", augmented);
26
27        for (k, v) in &cmd.env {
28            builder.env(k, v);
29        }
30
31        let output = builder.output().map_err(|e| {
32            if e.kind() == std::io::ErrorKind::NotFound {
33                SubstrateError::NotFound(cmd.program.clone())
34            } else {
35                SubstrateError::Io(e)
36            }
37        })?;
38
39        let duration_ms = start.elapsed().as_millis() as u64;
40
41        Ok(SubstrateResult {
42            stdout: output.stdout,
43            stderr: output.stderr,
44            exit_code: output.status.code().unwrap_or(-1),
45            duration_ms,
46        })
47    }
48
49    fn is_available(&self) -> bool {
50        true
51    }
52}