feat(setup): auto-detect npx path during installation

The setup wizard now auto-detects the npx/npx.cmd path via
`which npx` (Unix) or `where npx.cmd` (Windows) and writes it
to [browser].node_path in config.toml.

Changes:
- detect_npx_path(): cross-platform detection using which/where
- SerializedConfig now includes optional [browser] section
- Flat config parser (main.rs) handles [browser] section
- browser: BrowserSection threaded through AgentConfig everywhere
- init_browser() accepts node_path parameter with proper fallback
This commit is contained in:
2026-06-12 16:19:44 +05:30
parent 697c2c63b2
commit cc0ff65e89
2 changed files with 53 additions and 1 deletions

View File

@@ -424,6 +424,8 @@ fn write_config_file(config: &ProviderConfig) -> anyhow::Result<PathBuf> {
struct SerializedConfig {
provider: SerializedProvider,
agent: SerializedAgent,
#[serde(skip_serializing_if = "Option::is_none")]
browser: Option<SerializedBrowser>,
}
#[derive(Serialize)]
@@ -445,10 +447,46 @@ struct SerializedAgent {
name: String,
}
/// Serializable [browser] section for TOML output.
#[derive(Serialize)]
struct SerializedBrowser {
#[serde(skip_serializing_if = "Option::is_none")]
node_path: Option<String>,
}
fn is_false(v: &bool) -> bool {
!v
}
/// Auto-detect the path to npx/npx.cmd.
///
/// Tries `which npx` on Unix, `where npx.cmd` on Windows.
/// Returns `None` if not found on PATH.
fn detect_npx_path() -> Option<String> {
let (prog, arg) = if cfg!(target_os = "windows") {
("where", "npx.cmd")
} else {
("which", "npx")
};
std::process::Command::new(prog)
.arg(arg)
.output()
.ok()
.and_then(|out| {
if out.status.success() {
let path = String::from_utf8_lossy(&out.stdout)
.lines()
.next()?
.trim()
.to_string();
if !path.is_empty() { Some(path) } else { None }
} else {
None
}
})
}
impl From<&ProviderConfig> for SerializedConfig {
fn from(c: &ProviderConfig) -> Self {
let kind_str = match c.kind {
@@ -475,6 +513,9 @@ impl From<&ProviderConfig> for SerializedConfig {
agent: SerializedAgent {
name: "gyandoot".to_string(),
},
browser: detect_npx_path().map(|np| SerializedBrowser {
node_path: Some(np),
}),
}
}
}

View File

@@ -293,6 +293,8 @@ fn load_and_convert_config(path: &std::path::Path) -> Result<gyandoot_core::conf
struct FlatConfig {
provider: FlatProvider,
agent: Option<FlatAgent>,
#[serde(default)]
browser: Option<FlatBrowser>,
}
#[derive(serde::Deserialize)]
@@ -316,6 +318,12 @@ fn load_and_convert_config(path: &std::path::Path) -> Result<gyandoot_core::conf
name: Option<String>,
}
#[derive(serde::Deserialize)]
struct FlatBrowser {
#[serde(default)]
node_path: Option<String>,
}
let flat: FlatConfig = toml::from_str(&content)?;
tracing::info!("Loaded config from {} (converted from flat [provider] format)", path.display());
@@ -346,7 +354,10 @@ fn load_and_convert_config(path: &std::path::Path) -> Result<gyandoot_core::conf
platform: gyandoot_core::config::PlatformSection::default(),
memory: gyandoot_core::config::MemorySection::default(),
gateway: gyandoot_core::config::GatewaySection::default(),
browser: gyandoot_core::config::BrowserSection::default(),
browser: gyandoot_core::config::BrowserSection {
node_path: flat.browser.and_then(|b| b.node_path),
..gyandoot_core::config::BrowserSection::default()
},
};
Ok(gyandoot_core::config::AgentConfig::from(gyandoot_cfg))