use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand};
use log::{error, info};
use serde_json::Value;
use std::{fs, path::PathBuf};
mod app_error;
mod cache;
mod deno_runner;
mod mcp;
mod model;
mod python_runner;
use crate::cache::CodeFileCache;
use crate::model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
#[derive(Parser)]
#[command(name = "run_code_rmcp")]
#[command(about = "Execute JavaScript and Python code using MCP SDK", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long)]
show_logs: bool,
#[arg(short, long)]
use_mcp: bool,
#[arg(short = 'c', long)]
clear_cache: bool,
}
#[derive(Subcommand)]
enum Commands {
Js(CodeArgs),
Ts(CodeArgs),
Python(CodeArgs),
ClearCache {
#[arg(short, long)]
language: String,
},
}
#[derive(Args)]
struct CodeArgs {
#[arg(short, long)]
file: Option<PathBuf>,
#[arg(short, long)]
code: Option<String>,
#[arg(short, long)]
params: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let mut builder = env_logger::Builder::from_default_env();
if cli.show_logs {
builder.filter_level(log::LevelFilter::Info);
}
builder.init();
if let Commands::ClearCache { language } = &cli.command {
match language.to_lowercase().as_str() {
"js" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Js).await?;
info!("已清除 JavaScript 缓存");
}
"ts" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Ts).await?;
info!("已清除 TypeScript 缓存");
}
"python" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Python).await?;
info!("已清除 Python 缓存");
}
"all" => {
CodeFileCache::clear_all_cache().await?;
info!("已清除所有缓存");
}
_ => {
info!("无效的语言类型,可选项: js, ts, python, all");
return Ok(());
}
}
return Ok(());
}
let params = match &cli.command {
Commands::Js(args) => parse_params(&args.params)?,
Commands::Ts(args) => parse_params(&args.params)?,
Commands::Python(args) => parse_params(&args.params)?,
_ => None,
};
let (code, language) = match &cli.command {
Commands::Js(args) => (get_code(args)?, LanguageScript::Js),
Commands::Ts(args) => (get_code(args)?, LanguageScript::Ts),
Commands::Python(args) => (get_code(args)?, LanguageScript::Python),
_ => unreachable!(),
};
if cli.clear_cache {
CodeFileCache::clear_cache_by_language(&language).await?;
info!("已清除 {language:?} 缓存");
}
let result = if cli.use_mcp {
CodeExecutor::execute_with_params_compat(&code, language, params).await?
} else {
CodeExecutor::execute_with_params_compat(&code, language, params).await?
};
print_result(result);
Ok(())
}
fn get_code(args: &CodeArgs) -> Result<String> {
if let Some(file) = &args.file {
fs::read_to_string(file).context("Failed to read code file")
} else if let Some(code) = &args.code {
Ok(code.clone())
} else {
anyhow::bail!("Either file or code must be provided")
}
}
fn parse_params(params_str: &Option<String>) -> Result<Option<Value>> {
match params_str {
Some(s) if !s.is_empty() => {
let parsed: Value =
serde_json::from_str(s).context("Failed to parse parameters as JSON")?;
Ok(Some(parsed))
}
_ => Ok(None),
}
}
fn print_result(result: CodeScriptExecutionResult) {
if !result.logs.is_empty() {
info!("--- Logs ---");
for log in result.logs {
info!("{log}");
}
info!("------------");
}
if let Some(result_val) = result.result {
match serde_json::to_string_pretty(&result_val) {
Ok(json_str) => info!("Result: {json_str}"),
Err(_) => info!("Result: {result_val}"),
}
}
if let Some(error) = result.error {
error!("Error: {error}");
}
}