scripts / setup.js
#!/usr/bin/env node
/**
* postinstall 腳本 - 改進版
* 職責:
* 1. 從 GitHub clone MaverickMCP 專案到 maverick-mcp/ 目錄
* 2. 建立 Python venv (優先使用 Python 3.12-3.13)
* 3. 分階段安裝 Python 套件(核心 + backtesting + research)並逐一驗證
*
* 用法:
* node scripts/setup.js # 標準安裝(venv 存在時跳過)
* node scripts/setup.js --force # 強制重新安裝(保留 venv,重裝套件)
* node scripts/setup.js --clean # 完全清除 venv 後重裝
*/
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const PLUGIN_ROOT = path.resolve(__dirname, "..");
const MAVERICK_DIR = path.join(PLUGIN_ROOT, "maverick-mcp");
const REPO_URL = "https://github.com/wshobson/maverick-mcp.git";
// --- 命令列參數解析 ---
const ARGS = process.argv.slice(2);
const FORCE_REINSTALL = ARGS.includes("--force");
const CLEAN_INSTALL = ARGS.includes("--clean");
function log(msg) {
console.log(`[setup] ${msg}`);
}
function run(cmd, options = {}) {
log(`> ${cmd}`);
execSync(cmd, {
stdio: "inherit",
cwd: options.cwd || PLUGIN_ROOT,
...options,
});
}
/**
* 尋找最佳的 Python 版本
* 優先順序:3.12 > 3.13 > 3.11 > 3.10
* 避免使用 3.14+ (numba 不支援)
*/
function findPython() {
// 優先尋找 3.12 和 3.13(numba 完全支援)
const preferredVersions = [
{ cmd: "python3.12", minVersion: [3, 12], maxVersion: [3, 12] },
{ cmd: "python3.13", minVersion: [3, 13], maxVersion: [3, 13] },
{ cmd: "python3.11", minVersion: [3, 11], maxVersion: [3, 11] },
{ cmd: "python3.10", minVersion: [3, 10], maxVersion: [3, 10] },
];
// 也嘗試通用命令
const fallbackCommands = ["python3", "python"];
const allCandidates = [
...preferredVersions.map(p => p.cmd),
...fallbackCommands,
];
for (const candidate of allCandidates) {
try {
const out = execSync(`${candidate} --version`, { stdio: "pipe" }).toString();
const match = out.match(/Python (\d+)\.(\d+)\.(\d+)/);
if (match) {
const major = parseInt(match[1]);
const minor = parseInt(match[2]);
const patch = parseInt(match[3]);
// 檢查版本是否 >= 3.10 且 < 3.14
if (major === 3 && minor >= 10 && minor < 14) {
log(`Found compatible Python: ${candidate} (${major}.${minor}.${patch})`);
return candidate;
} else if (major === 3 && minor >= 14) {
log(`WARNING: ${candidate} is Python ${major}.${minor}, which is too new for numba.`);
}
}
} catch {
// 繼續嘗試下一個
}
}
// 如果找不到相容版本,給出明確錯誤
console.error("[setup] ERROR: No compatible Python version found!");
console.error("[setup] MaverickMCP requires Python 3.10, 3.11, 3.12, or 3.13.");
console.error("[setup] Your system has Python 3.14+, which is not supported by numba.");
console.error("");
console.error("[setup] Please install Python 3.12 or 3.13:");
console.error("[setup] Ubuntu/Debian: sudo apt install python3.12 python3.12-venv");
console.error("[setup] macOS: brew install python@3.12");
console.error("[setup] Windows: Download from https://www.python.org/downloads/");
console.error("");
process.exit(1);
}
function getPipCmd() {
return process.platform === "win32"
? path.join(".venv", "Scripts", "pip.exe")
: path.join(".venv", "bin", "pip");
}
function getPythonInVenv() {
return process.platform === "win32"
? path.join(MAVERICK_DIR, ".venv", "Scripts", "python.exe")
: path.join(MAVERICK_DIR, ".venv", "bin", "python");
}
function getVenvMarker() {
return getPythonInVenv();
}
/**
* 驗證特定 Python 套件是否已安裝在 venv 中
*/
function verifyPackageInstalled(pythonInVenv, packageName) {
try {
execSync(`${pythonInVenv} -c "import ${packageName}"`, {
stdio: "pipe",
cwd: MAVERICK_DIR,
});
return true;
} catch {
return false;
}
}
function main() {
log("=== MaverickMCP LM Studio Plugin Setup ===");
log(`Mode: ${CLEAN_INSTALL ? "clean" : FORCE_REINSTALL ? "force-reinstall" : "standard"}`);
// --- 1. Clone MaverickMCP ---
if (!fs.existsSync(MAVERICK_DIR)) {
log("Cloning MaverickMCP repository...");
run(`git clone --depth 1 ${REPO_URL} maverick-mcp`);
} else {
log("MaverickMCP directory already exists, skipping clone.");
}
const python = findPython();
log(`Host Python: ${python}`);
// --- 2. 處理 venv ---
const venvMarker = getVenvMarker();
const venvExists = fs.existsSync(venvMarker);
if (CLEAN_INSTALL && venvExists) {
log("Clean install: removing existing .venv...");
fs.rmSync(path.join(MAVERICK_DIR, ".venv"), { recursive: true, force: true });
}
if (!fs.existsSync(getVenvMarker())) {
log(`Creating virtual environment using ${python}...`);
run(`${python} -m venv .venv`, { cwd: MAVERICK_DIR });
} else if (!FORCE_REINSTALL && !CLEAN_INSTALL) {
// 標準模式:venv 存在時仍需驗證套件完整性
log("Virtual environment exists. Verifying packages...");
}
const pip = getPipCmd();
const pythonInVenv = getPythonInVenv();
// --- 3. 升級 pip ---
log("Upgrading pip, setuptools, wheel...");
run(`${pip} install --upgrade pip setuptools wheel`, { cwd: MAVERICK_DIR });
// --- 4. 分階段安裝 + 驗證 ---
const INSTALL_STAGES = [
{
name: "core",
spec: ".",
verify: "maverick",
description: "Core MaverickMCP (37 tools)",
required: true,
},
{
name: "backtesting",
spec: ".[backtesting]",
verify: "vectorbt",
description: "Backtesting extra (12 tools, VectorBT)",
required: false,
fallbackPackages: [
"vectorbt>=1.0.0",
"numba>=0.61.2",
"scikit-learn>=1.9.0",
"scipy>=1.17.1",
"pandas-ta>=0.4.71b0",
],
},
{
name: "research",
spec: ".[research]",
verify: "langgraph",
description: "Research extra (3 tools, LangGraph)",
required: false,
fallbackPackages: [
"langchain>=1.3.9",
"langchain-anthropic>=1.4.6",
"langchain-community>=0.4.2",
"langchain-openai>=1.2.2",
"langgraph>=1.2.4",
"exa-py>=2.13.0",
],
},
];
let coreOk = false;
let backtestingOk = false;
let researchOk = false;
for (const stage of INSTALL_STAGES) {
// 先驗證是否已安裝
if (!FORCE_REINSTALL && !CLEAN_INSTALL && verifyPackageInstalled(pythonInVenv, stage.verify)) {
log(`✓ ${stage.name} already installed (${stage.verify} importable)`);
if (stage.name === "core") coreOk = true;
if (stage.name === "backtesting") backtestingOk = true;
if (stage.name === "research") researchOk = true;
continue;
}
log(`Installing ${stage.description}...`);
try {
run(`${pip} install -e "${stage.spec}"`, { cwd: MAVERICK_DIR });
} catch (err) {
log(`WARNING: Failed to install ${stage.name} via editable mode.`);
if (stage.fallbackPackages) {
log(`Attempting to install packages individually...`);
try {
const packageList = stage.fallbackPackages.map(p => `"${p}"`).join(" ");
run(`${pip} install ${packageList}`, { cwd: MAVERICK_DIR });
} catch (fallbackErr) {
log(`ERROR: Failed to install ${stage.name} packages: ${fallbackErr.message}`);
if (stage.required) {
log(`FATAL: ${stage.name} is required but installation failed.`);
process.exit(1);
} else {
log(`Continuing without ${stage.name} (optional).`);
}
}
} else {
if (stage.required) {
log(`FATAL: ${stage.name} is required but installation failed.`);
process.exit(1);
}
}
}
// 再次驗證
const installed = verifyPackageInstalled(pythonInVenv, stage.verify);
if (installed) {
log(`✓ ${stage.name} successfully installed and verified`);
if (stage.name === "core") coreOk = true;
if (stage.name === "backtesting") backtestingOk = true;
if (stage.name === "research") researchOk = true;
} else {
log(`WARNING: ${stage.name} package install completed but import test failed.`);
if (stage.required) {
log(`FATAL: Required package ${stage.verify} is not importable.`);
process.exit(1);
}
}
}
// --- 5. 環境檔 ---
const envSrc = path.join(MAVERICK_DIR, ".env.example");
const envDst = path.join(MAVERICK_DIR, ".env");
if (fs.existsSync(envSrc) && !fs.existsSync(envDst)) {
log("Creating .env from .env.example...");
fs.copyFileSync(envSrc, envDst);
}
// --- 6. 最終報告 ---
console.log("\n" + "=".repeat(70));
console.log(" MaverickMCP Setup Summary");
console.log("=".repeat(70));
console.log(` Core (37 tools) : ${coreOk ? "✓ INSTALLED" : "✗ MISSING"}`);
console.log(` Backtesting (12 tools): ${backtestingOk ? "✓ INSTALLED" : "✗ NOT INSTALLED"}`);
console.log(` Research (3 tools) : ${researchOk ? "✓ INSTALLED" : "✗ NOT INSTALLED"}`);
console.log("=".repeat(70));
const total = (coreOk ? 37 : 0) + (backtestingOk ? 12 : 0) + (researchOk ? 3 : 0);
console.log(` Total tools available : ${total}`);
console.log("=".repeat(70));
if (coreOk && !backtestingOk) {
console.log("\n ⚠ To install backtesting later:");
console.log(` 1. Install Python 3.12 or 3.13`);
console.log(` 2. cd ${MAVERICK_DIR}`);
console.log(` 3. python3.12 -m venv .venv --clear`);
console.log(` 4. .venv/bin/pip install -e ".[backtesting]"`);
}
if (coreOk && !researchOk) {
console.log("\n ⚠ To install research later:");
console.log(` cd ${MAVERICK_DIR}`);
console.log(` ${pip} install -e ".[research]"`);
}
console.log("\n ✓ Setup complete!\n");
}
try {
main();
} catch (error) {
console.error(`[setup] FATAL ERROR: ${error.message}`);
process.exit(1);
}scripts / setup.js
#!/usr/bin/env node
/**
* postinstall 腳本 - 改進版
* 職責:
* 1. 從 GitHub clone MaverickMCP 專案到 maverick-mcp/ 目錄
* 2. 建立 Python venv (優先使用 Python 3.12-3.13)
* 3. 分階段安裝 Python 套件(核心 + backtesting + research)並逐一驗證
*
* 用法:
* node scripts/setup.js # 標準安裝(venv 存在時跳過)
* node scripts/setup.js --force # 強制重新安裝(保留 venv,重裝套件)
* node scripts/setup.js --clean # 完全清除 venv 後重裝
*/
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const PLUGIN_ROOT = path.resolve(__dirname, "..");
const MAVERICK_DIR = path.join(PLUGIN_ROOT, "maverick-mcp");
const REPO_URL = "https://github.com/wshobson/maverick-mcp.git";
// --- 命令列參數解析 ---
const ARGS = process.argv.slice(2);
const FORCE_REINSTALL = ARGS.includes("--force");
const CLEAN_INSTALL = ARGS.includes("--clean");
function log(msg) {
console.log(`[setup] ${msg}`);
}
function run(cmd, options = {}) {
log(`> ${cmd}`);
execSync(cmd, {
stdio: "inherit",
cwd: options.cwd || PLUGIN_ROOT,
...options,
});
}
/**
* 尋找最佳的 Python 版本
* 優先順序:3.12 > 3.13 > 3.11 > 3.10
* 避免使用 3.14+ (numba 不支援)
*/
function findPython() {
// 優先尋找 3.12 和 3.13(numba 完全支援)
const preferredVersions = [
{ cmd: "python3.12", minVersion: [3, 12], maxVersion: [3, 12] },
{ cmd: "python3.13", minVersion: [3, 13], maxVersion: [3, 13] },
{ cmd: "python3.11", minVersion: [3, 11], maxVersion: [3, 11] },
{ cmd: "python3.10", minVersion: [3, 10], maxVersion: [3, 10] },
];
// 也嘗試通用命令
const fallbackCommands = ["python3", "python"];
const allCandidates = [
...preferredVersions.map(p => p.cmd),
...fallbackCommands,
];
for (const candidate of allCandidates) {
try {
const out = execSync(`${candidate} --version`, { stdio: "pipe" }).toString();
const match = out.match(/Python (\d+)\.(\d+)\.(\d+)/);
if (match) {
const major = parseInt(match[1]);
const minor = parseInt(match[2]);
const patch = parseInt(match[3]);
// 檢查版本是否 >= 3.10 且 < 3.14
if (major === 3 && minor >= 10 && minor < 14) {
log(`Found compatible Python: ${candidate} (${major}.${minor}.${patch})`);
return candidate;
} else if (major === 3 && minor >= 14) {
log(`WARNING: ${candidate} is Python ${major}.${minor}, which is too new for numba.`);
}
}
} catch {
// 繼續嘗試下一個
}
}
// 如果找不到相容版本,給出明確錯誤
console.error("[setup] ERROR: No compatible Python version found!");
console.error("[setup] MaverickMCP requires Python 3.10, 3.11, 3.12, or 3.13.");
console.error("[setup] Your system has Python 3.14+, which is not supported by numba.");
console.error("");
console.error("[setup] Please install Python 3.12 or 3.13:");
console.error("[setup] Ubuntu/Debian: sudo apt install python3.12 python3.12-venv");
console.error("[setup] macOS: brew install python@3.12");
console.error("[setup] Windows: Download from https://www.python.org/downloads/");
console.error("");
process.exit(1);
}
function getPipCmd() {
return process.platform === "win32"
? path.join(".venv", "Scripts", "pip.exe")
: path.join(".venv", "bin", "pip");
}
function getPythonInVenv() {
return process.platform === "win32"
? path.join(MAVERICK_DIR, ".venv", "Scripts", "python.exe")
: path.join(MAVERICK_DIR, ".venv", "bin", "python");
}
function getVenvMarker() {
return getPythonInVenv();
}
/**
* 驗證特定 Python 套件是否已安裝在 venv 中
*/
function verifyPackageInstalled(pythonInVenv, packageName) {
try {
execSync(`${pythonInVenv} -c "import ${packageName}"`, {
stdio: "pipe",
cwd: MAVERICK_DIR,
});
return true;
} catch {
return false;
}
}
function main() {
log("=== MaverickMCP LM Studio Plugin Setup ===");
log(`Mode: ${CLEAN_INSTALL ? "clean" : FORCE_REINSTALL ? "force-reinstall" : "standard"}`);
// --- 1. Clone MaverickMCP ---
if (!fs.existsSync(MAVERICK_DIR)) {
log("Cloning MaverickMCP repository...");
run(`git clone --depth 1 ${REPO_URL} maverick-mcp`);
} else {
log("MaverickMCP directory already exists, skipping clone.");
}
const python = findPython();
log(`Host Python: ${python}`);
// --- 2. 處理 venv ---
const venvMarker = getVenvMarker();
const venvExists = fs.existsSync(venvMarker);
if (CLEAN_INSTALL && venvExists) {
log("Clean install: removing existing .venv...");
fs.rmSync(path.join(MAVERICK_DIR, ".venv"), { recursive: true, force: true });
}
if (!fs.existsSync(getVenvMarker())) {
log(`Creating virtual environment using ${python}...`);
run(`${python} -m venv .venv`, { cwd: MAVERICK_DIR });
} else if (!FORCE_REINSTALL && !CLEAN_INSTALL) {
// 標準模式:venv 存在時仍需驗證套件完整性
log("Virtual environment exists. Verifying packages...");
}
const pip = getPipCmd();
const pythonInVenv = getPythonInVenv();
// --- 3. 升級 pip ---
log("Upgrading pip, setuptools, wheel...");
run(`${pip} install --upgrade pip setuptools wheel`, { cwd: MAVERICK_DIR });
// --- 4. 分階段安裝 + 驗證 ---
const INSTALL_STAGES = [
{
name: "core",
spec: ".",
verify: "maverick",
description: "Core MaverickMCP (37 tools)",
required: true,
},
{
name: "backtesting",
spec: ".[backtesting]",
verify: "vectorbt",
description: "Backtesting extra (12 tools, VectorBT)",
required: false,
fallbackPackages: [
"vectorbt>=1.0.0",
"numba>=0.61.2",
"scikit-learn>=1.9.0",
"scipy>=1.17.1",
"pandas-ta>=0.4.71b0",
],
},
{
name: "research",
spec: ".[research]",
verify: "langgraph",
description: "Research extra (3 tools, LangGraph)",
required: false,
fallbackPackages: [
"langchain>=1.3.9",
"langchain-anthropic>=1.4.6",
"langchain-community>=0.4.2",
"langchain-openai>=1.2.2",
"langgraph>=1.2.4",
"exa-py>=2.13.0",
],
},
];
let coreOk = false;
let backtestingOk = false;
let researchOk = false;
for (const stage of INSTALL_STAGES) {
// 先驗證是否已安裝
if (!FORCE_REINSTALL && !CLEAN_INSTALL && verifyPackageInstalled(pythonInVenv, stage.verify)) {
log(`✓ ${stage.name} already installed (${stage.verify} importable)`);
if (stage.name === "core") coreOk = true;
if (stage.name === "backtesting") backtestingOk = true;
if (stage.name === "research") researchOk = true;
continue;
}
log(`Installing ${stage.description}...`);
try {
run(`${pip} install -e "${stage.spec}"`, { cwd: MAVERICK_DIR });
} catch (err) {
log(`WARNING: Failed to install ${stage.name} via editable mode.`);
if (stage.fallbackPackages) {
log(`Attempting to install packages individually...`);
try {
const packageList = stage.fallbackPackages.map(p => `"${p}"`).join(" ");
run(`${pip} install ${packageList}`, { cwd: MAVERICK_DIR });
} catch (fallbackErr) {
log(`ERROR: Failed to install ${stage.name} packages: ${fallbackErr.message}`);
if (stage.required) {
log(`FATAL: ${stage.name} is required but installation failed.`);
process.exit(1);
} else {
log(`Continuing without ${stage.name} (optional).`);
}
}
} else {
if (stage.required) {
log(`FATAL: ${stage.name} is required but installation failed.`);
process.exit(1);
}
}
}
// 再次驗證
const installed = verifyPackageInstalled(pythonInVenv, stage.verify);
if (installed) {
log(`✓ ${stage.name} successfully installed and verified`);
if (stage.name === "core") coreOk = true;
if (stage.name === "backtesting") backtestingOk = true;
if (stage.name === "research") researchOk = true;
} else {
log(`WARNING: ${stage.name} package install completed but import test failed.`);
if (stage.required) {
log(`FATAL: Required package ${stage.verify} is not importable.`);
process.exit(1);
}
}
}
// --- 5. 環境檔 ---
const envSrc = path.join(MAVERICK_DIR, ".env.example");
const envDst = path.join(MAVERICK_DIR, ".env");
if (fs.existsSync(envSrc) && !fs.existsSync(envDst)) {
log("Creating .env from .env.example...");
fs.copyFileSync(envSrc, envDst);
}
// --- 6. 最終報告 ---
console.log("\n" + "=".repeat(70));
console.log(" MaverickMCP Setup Summary");
console.log("=".repeat(70));
console.log(` Core (37 tools) : ${coreOk ? "✓ INSTALLED" : "✗ MISSING"}`);
console.log(` Backtesting (12 tools): ${backtestingOk ? "✓ INSTALLED" : "✗ NOT INSTALLED"}`);
console.log(` Research (3 tools) : ${researchOk ? "✓ INSTALLED" : "✗ NOT INSTALLED"}`);
console.log("=".repeat(70));
const total = (coreOk ? 37 : 0) + (backtestingOk ? 12 : 0) + (researchOk ? 3 : 0);
console.log(` Total tools available : ${total}`);
console.log("=".repeat(70));
if (coreOk && !backtestingOk) {
console.log("\n ⚠ To install backtesting later:");
console.log(` 1. Install Python 3.12 or 3.13`);
console.log(` 2. cd ${MAVERICK_DIR}`);
console.log(` 3. python3.12 -m venv .venv --clear`);
console.log(` 4. .venv/bin/pip install -e ".[backtesting]"`);
}
if (coreOk && !researchOk) {
console.log("\n ⚠ To install research later:");
console.log(` cd ${MAVERICK_DIR}`);
console.log(` ${pip} install -e ".[research]"`);
}
console.log("\n ✓ Setup complete!\n");
}
try {
main();
} catch (error) {
console.error(`[setup] FATAL ERROR: ${error.message}`);
process.exit(1);
}