pub mod admin;
#[cfg(all(feature = "comms", any(unix, windows)))]
pub mod agents;
pub mod choices;
pub mod code;
#[cfg(all(feature = "comms", any(unix, windows)))]
pub mod comms_daemon;
pub mod context;
pub mod exit;
pub mod git;
pub mod graph;
pub mod hook;
pub mod init;
pub mod init_gitignore;
pub mod init_rules;
pub mod init_settings;
pub mod interrupt;
pub mod memory;
#[cfg(all(feature = "comms", any(unix, windows)))]
pub mod registry;
pub mod render;
pub mod rescan;
#[cfg(all(feature = "shells", any(unix, windows)))]
pub mod shell;
pub mod web;
use std::io::Write;
use std::path::Path;
use std::time::Instant;
use anyhow::{Context, Result};
use clap::Subcommand;
use rmcp::ErrorData as McpError;
use rmcp::model::CallToolResult;
use crate::cli::render::Emit;
use crate::config::DocumentsCliOverrides;
#[derive(Subcommand, Debug)]
pub enum ToolCmd {
#[command(subcommand)]
Code(code::CodeCmd),
#[command(subcommand)]
Git(git::GitCmd),
#[command(subcommand)]
Graph(graph::GraphCmd),
#[command(subcommand)]
Memory(memory::MemoryCmd),
#[command(subcommand)]
Web(web::WebCmd),
#[command(subcommand)]
Admin(admin::AdminCmd),
Status,
#[cfg(all(feature = "shells", any(unix, windows)))]
#[command(subcommand)]
Shell(shell::ShellCmd),
}
pub(crate) fn resolve_path(server: &crate::mcp::BasemindServer, path: &str) -> crate::path::RelPath {
match crate::path::normalize_query_path(path, &server.state.shared.root) {
Some(rel) => crate::path::RelPath::from(rel),
None => crate::path::RelPath::from(path),
}
}
pub fn run_tool(tool: &str, result: Result<CallToolResult, McpError>) -> Result<CallToolResult> {
result.map_err(|e| {
let message = e.message.to_string();
if e.code == rmcp::model::ErrorCode::INVALID_PARAMS {
return exit::CliExit::usage(format!("{tool}: {message}"));
}
if message.contains("index not available") {
return exit::CliExit::busy(format!(
"{tool}: {message}. Another basemind process holds the index lock and no daemon is \
reachable to forward to. Start the daemon with `basemind comms start` (it becomes the \
sole writer and serves this call), or stop the process holding the lock and retry."
));
}
anyhow::anyhow!("{tool}: {e}")
})
}
pub fn run(
root: &Path,
view: &str,
documents: DocumentsCliOverrides,
json: bool,
process_started: Instant,
cmd: ToolCmd,
) -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.context("build tokio runtime")?;
let intent = context::Intent::Read;
runtime.block_on(async move {
let server = context::build_server_for(root, view, documents, intent)?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
let opts = Emit {
json,
startup_us: u64::try_from(process_started.elapsed().as_micros()).unwrap_or(u64::MAX),
};
match cmd {
ToolCmd::Code(c) => code::run(&server, c, &opts, &mut out).await?,
ToolCmd::Git(g) => git::run(&server, g, &opts, &mut out).await?,
ToolCmd::Graph(g) => graph::run(&server, g, &opts, &mut out).await?,
ToolCmd::Memory(m) => memory::run(&server, m, &opts, &mut out).await?,
ToolCmd::Web(w) => web::run(&server, w, &opts, &mut out).await?,
ToolCmd::Admin(a) => admin::run(&server, a, &opts, &mut out).await?,
ToolCmd::Status => admin::run_status(&server, &opts, &mut out).await?,
#[cfg(all(feature = "shells", any(unix, windows)))]
ToolCmd::Shell(s) => shell::run(&server, s, &opts, &mut out).await?,
}
out.flush().context("flush stdout")?;
Ok(())
})
}
pub fn run_cache(root: &Path, cmd: admin::CacheCmd, json: bool) -> Result<()> {
let stdout = std::io::stdout();
let mut out = stdout.lock();
admin::run_cache(root, cmd, json, &mut out)?;
out.flush().context("flush stdout")?;
Ok(())
}