navi-core 0.3.7

Local agentic engine and terminal-first coding agent.
Documentation
use anyhow::Result;
use serde_json::json;
use std::path::Path;

use super::helpers;
use super::{PKG_OUTPUT_LIMIT_BYTES, run_pkg};
use crate::tool::ToolResult;

pub(super) async fn cmd_install(
    project_root: &Path,
    invocation_id: &str,
    manager: &str,
) -> Result<ToolResult> {
    let cmd = match manager {
        "dart" => "dart pub get",
        "npm" => "npm install",
        "bun" => "bun install",
        "cargo" => "cargo fetch",
        "go" => "go mod download",
        _ => anyhow::bail!("unsupported package manager: {manager}"),
    };

    let (ok, stdout, stderr) = run_pkg(project_root, &[cmd]).await?;

    Ok(ToolResult {
        invocation_id: invocation_id.to_string(),
        ok,
        output: if ok {
            json!({
                "schema_version": helpers::SPECIALIZED_SCHEMA_VERSION,
                "status": "success",
                "manager": manager,
                "output": helpers::truncate_string(stdout, PKG_OUTPUT_LIMIT_BYTES),
            })
        } else {
            helpers::tool_error(
                "package_install_failed",
                format!("{manager} install failed"),
                true,
                None,
                Some(helpers::truncate_string(stderr, PKG_OUTPUT_LIMIT_BYTES)),
            )
        },
    })
}

pub(super) async fn cmd_add(
    project_root: &Path,
    invocation_id: &str,
    manager: &str,
    packages: &[String],
    dev: bool,
) -> Result<ToolResult> {
    if packages.is_empty() {
        return Ok(ToolResult {
            invocation_id: invocation_id.to_string(),
            ok: false,
            output: helpers::tool_error(
                "missing_packages",
                "no packages specified for add",
                true,
                Some("Pass packages, for example: [\"serde\"]."),
                None,
            ),
        });
    }

    let pkgs = packages.join(" ");
    let cmd = match manager {
        "dart" => {
            if dev {
                format!("dart pub add --dev {pkgs}")
            } else {
                format!("dart pub add {pkgs}")
            }
        }
        "npm" => {
            if dev {
                format!("npm install --save-dev {pkgs}")
            } else {
                format!("npm install {pkgs}")
            }
        }
        "bun" => {
            if dev {
                format!("bun add --dev {pkgs}")
            } else {
                format!("bun add {pkgs}")
            }
        }
        "cargo" => {
            let mut args = format!("cargo add {pkgs}");
            if dev {
                args.push_str(" --dev");
            }
            args
        }
        "go" => format!("go get {pkgs}"),
        _ => anyhow::bail!("unsupported package manager: {manager}"),
    };

    let (ok, stdout, stderr) = run_pkg(project_root, &[&cmd]).await?;

    let installed: Vec<serde_json::Value> = packages
        .iter()
        .map(|p| json!({ "name": p, "version": "latest" }))
        .collect();

    Ok(ToolResult {
        invocation_id: invocation_id.to_string(),
        ok,
        output: if ok {
            json!({
                "schema_version": helpers::SPECIALIZED_SCHEMA_VERSION,
                "status": "success",
                "manager": manager,
                "installed": installed,
                "lockfile_changed": true,
                "output": helpers::truncate_string(stdout, PKG_OUTPUT_LIMIT_BYTES),
            })
        } else {
            helpers::tool_error(
                "package_add_failed",
                format!("{manager} add failed"),
                true,
                None,
                Some(helpers::truncate_string(stderr, PKG_OUTPUT_LIMIT_BYTES)),
            )
        },
    })
}

pub(super) async fn cmd_remove(
    project_root: &Path,
    invocation_id: &str,
    manager: &str,
    packages: &[String],
) -> Result<ToolResult> {
    if packages.is_empty() {
        return Ok(ToolResult {
            invocation_id: invocation_id.to_string(),
            ok: false,
            output: helpers::tool_error(
                "missing_packages",
                "no packages specified for remove",
                true,
                Some("Pass packages, for example: [\"serde\"]."),
                None,
            ),
        });
    }

    let pkgs = packages.join(" ");
    let cmd = match manager {
        "dart" => format!("dart pub remove {pkgs}"),
        "npm" => format!("npm uninstall {pkgs}"),
        "bun" => format!("bun remove {pkgs}"),
        "cargo" => format!("cargo rm {pkgs}"),
        "go" => format!("go mod edit -droprequire {pkgs} && go mod tidy"),
        _ => anyhow::bail!("unsupported package manager: {manager}"),
    };

    let (ok, stdout, stderr) = run_pkg(project_root, &[&cmd]).await?;

    Ok(ToolResult {
        invocation_id: invocation_id.to_string(),
        ok,
        output: if ok {
            json!({
                "schema_version": helpers::SPECIALIZED_SCHEMA_VERSION,
                "status": "success",
                "manager": manager,
                "removed": packages,
                "lockfile_changed": true,
                "output": helpers::truncate_string(stdout, PKG_OUTPUT_LIMIT_BYTES),
            })
        } else {
            helpers::tool_error(
                "package_remove_failed",
                format!("{manager} remove failed"),
                true,
                None,
                Some(helpers::truncate_string(stderr, PKG_OUTPUT_LIMIT_BYTES)),
            )
        },
    })
}

pub(super) async fn cmd_update(
    project_root: &Path,
    invocation_id: &str,
    manager: &str,
    packages: &[String],
) -> Result<ToolResult> {
    let cmd = if packages.is_empty() {
        match manager {
            "dart" => "dart pub upgrade".to_string(),
            "npm" => "npm update".to_string(),
            "bun" => "bun update".to_string(),
            "cargo" => "cargo update".to_string(),
            "go" => "go get -u ./...".to_string(),
            _ => anyhow::bail!("unsupported package manager: {manager}"),
        }
    } else {
        let pkgs = packages.join(" ");
        match manager {
            "dart" => format!("dart pub upgrade {pkgs}"),
            "npm" => format!("npm update {pkgs}"),
            "bun" => format!("bun update {pkgs}"),
            "cargo" => format!("cargo update {pkgs}"),
            "go" => format!("go get -u {pkgs}"),
            _ => anyhow::bail!("unsupported package manager: {manager}"),
        }
    };

    let (ok, stdout, stderr) = run_pkg(project_root, &[&cmd]).await?;

    Ok(ToolResult {
        invocation_id: invocation_id.to_string(),
        ok,
        output: if ok {
            json!({
                "schema_version": helpers::SPECIALIZED_SCHEMA_VERSION,
                "status": "success",
                "manager": manager,
                "lockfile_changed": true,
                "output": helpers::truncate_string(stdout, PKG_OUTPUT_LIMIT_BYTES),
            })
        } else {
            helpers::tool_error(
                "package_update_failed",
                format!("{manager} update failed"),
                true,
                None,
                Some(helpers::truncate_string(stderr, PKG_OUTPUT_LIMIT_BYTES)),
            )
        },
    })
}