#!/usr/bin/env node
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { createGunzip } = require('zlib');
const { pipeline } = require('stream/promises');
const VERSION = '0.3.0';
const REPO = 'standardbeagle/devtool-mcp';
const BINARY_NAME = 'devtool-mcp';
// Platform/architecture mapping
const PLATFORMS = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows',
};
const ARCHS = {
x64: 'amd64',
arm64: 'arm64',
};
function getPlatform() {
const platform = PLATFORMS[process.platform];
if (!platform) {
throw new Error(`Unsupported platform: ${process.platform}`);
}
return platform;
}
function getArch() {
const arch = ARCHS[process.arch];
if (!arch) {
throw new Error(`Unsupported architecture: ${process.arch}`);
}
return arch;
}
function getBinaryName() {
return process.platform === 'win32' ? `${BINARY_NAME}.exe` : BINARY_NAME;
}
function getDownloadUrl() {
const platform = getPlatform();
const arch = getArch();
const ext = platform === 'windows' ? '.exe' : '';
// GitHub release asset URL pattern
return `https://github.com/${REPO}/releases/download/v${VERSION}/${BINARY_NAME}-${platform}-${arch}${ext}`;
}
async function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
const protocol = url.startsWith('https') ? https : http;
const request = protocol.get(url, (response) => {
// Handle redirects
if (response.statusCode === 301 || response.statusCode === 302) {
file.close();
fs.unlinkSync(dest);
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
return;
}
if (response.statusCode !== 200) {
file.close();
fs.unlinkSync(dest);
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
return;
}
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
});
request.on('error', (err) => {
file.close();
fs.unlinkSync(dest);
reject(err);
});
});
}
async function install() {
const binDir = path.join(__dirname, '..', 'bin');
const binaryPath = path.join(binDir, getBinaryName());
// Create bin directory if it doesn't exist
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
// Check if binary already exists
if (fs.existsSync(binaryPath)) {
console.log(`${BINARY_NAME} binary already exists, skipping download`);
return;
}
const url = getDownloadUrl();
console.log(`Downloading ${BINARY_NAME} v${VERSION}...`);
console.log(` Platform: ${getPlatform()}`);
console.log(` Architecture: ${getArch()}`);
console.log(` URL: ${url}`);
try {
await downloadFile(url, binaryPath);
// Make executable on Unix
if (process.platform !== 'win32') {
fs.chmodSync(binaryPath, 0o755);
}
console.log(`Successfully installed ${BINARY_NAME} to ${binaryPath}`);
} catch (error) {
console.error(`Failed to download ${BINARY_NAME}:`);
console.error(error.message);
console.error('');
console.error('You can manually download the binary from:');
console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
console.error('');
console.error('Or build from source:');
console.error(' git clone https://github.com/standardbeagle/devtool-mcp.git');
console.error(' cd devtool-mcp');
console.error(' make build');
process.exit(1);
}
}
install().catch((err) => {
console.error(err);
process.exit(1);
});