314 lines
8.3 KiB
JavaScript
314 lines
8.3 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* QwenClaw Installer for Qwen Code
|
||
*
|
||
* This script installs and configures QwenClaw as the default agent for Qwen Code.
|
||
*
|
||
* Usage: bun run install-qwenclaw.js
|
||
*/
|
||
|
||
import { spawn, execSync } from "child_process";
|
||
import { join } from "path";
|
||
import { existsSync, readFileSync, writeFile, mkdirSync } from "fs";
|
||
|
||
const QWENCLAW_DIR = join(process.env.HOME || process.env.USERPROFILE || "", "qwenclaw");
|
||
const QWEN_DIR = join(process.env.HOME || process.env.USERPROFILE || "", ".qwen");
|
||
const QWEN_SETTINGS_FILE = join(QWEN_DIR, "settings.json");
|
||
const QWEN_MCP_FILE = join(QWEN_DIR, "mcp.json");
|
||
|
||
const COLORS = {
|
||
cyan: "\x1b[36m",
|
||
green: "\x1b[32m",
|
||
yellow: "\x1b[33m",
|
||
red: "\x1b[31m",
|
||
magenta: "\x1b[35m",
|
||
reset: "\x1b[0m",
|
||
};
|
||
|
||
function log(message, color = "reset") {
|
||
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
||
}
|
||
|
||
function showBanner() {
|
||
log("\n" + "═".repeat(60), "cyan");
|
||
log(" 🐾 QWENCLAW INSTALLER FOR QWEN CODE", "cyan");
|
||
log("═".repeat(60), "cyan");
|
||
}
|
||
|
||
/**
|
||
* Check if QwenClaw is installed
|
||
*/
|
||
function checkInstallation() {
|
||
log("\n📦 Checking QwenClaw installation...", "cyan");
|
||
|
||
if (!existsSync(QWENCLAW_DIR)) {
|
||
log("❌ QwenClaw not found at: " + QWENCLAW_DIR, "red");
|
||
log("\n📥 Installing QwenClaw...", "yellow");
|
||
|
||
try {
|
||
const repoUrl = "https://github.rommark.dev/admin/QwenClaw-with-Auth.git";
|
||
execSync(`git clone ${repoUrl} "${QWENCLAW_DIR}"`, { stdio: "inherit" });
|
||
log("✅ QwenClaw cloned successfully", "green");
|
||
} catch (err) {
|
||
log(`❌ Failed to clone: ${err.message}`, "red");
|
||
process.exit(1);
|
||
}
|
||
} else {
|
||
log("✅ QwenClaw found at: " + QWENCLAW_DIR, "green");
|
||
}
|
||
|
||
// Install dependencies
|
||
log("\n📦 Installing dependencies...", "cyan");
|
||
try {
|
||
execSync(`cd "${QWENCLAW_DIR}" && bun install`, { stdio: "inherit" });
|
||
log("✅ Dependencies installed", "green");
|
||
} catch (err) {
|
||
log(`❌ Failed to install dependencies: ${err.message}`, "red");
|
||
process.exit(1);
|
||
}
|
||
|
||
// Install Playwright browsers
|
||
log("\n🌐 Installing Playwright browsers...", "cyan");
|
||
try {
|
||
execSync(`cd "${QWENCLAW_DIR}" && npx playwright install chromium`, { stdio: "inherit" });
|
||
log("✅ Playwright browsers installed", "green");
|
||
} catch (err) {
|
||
log(`⚠️ Playwright installation skipped: ${err.message}`, "yellow");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create Qwen directory if needed
|
||
*/
|
||
function ensureQwenDir() {
|
||
log("\n📁 Ensuring Qwen directory exists...", "cyan");
|
||
|
||
if (!existsSync(QWEN_DIR)) {
|
||
mkdirSync(QWEN_DIR, { recursive: true });
|
||
log("✅ Created Qwen directory: " + QWEN_DIR, "green");
|
||
} else {
|
||
log("✅ Qwen directory exists", "green");
|
||
}
|
||
|
||
// Create qwenclaw subdirectory
|
||
const qwenclawDataDir = join(QWEN_DIR, "qwenclaw");
|
||
if (!existsSync(qwenclawDataDir)) {
|
||
mkdirSync(qwenclawDataDir, { recursive: true });
|
||
log("✅ Created QwenClaw data directory", "green");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update Qwen Code settings
|
||
*/
|
||
function updateSettings() {
|
||
log("\n⚙️ Configuring Qwen Code settings...", "cyan");
|
||
|
||
let settings = {};
|
||
|
||
if (existsSync(QWEN_SETTINGS_FILE)) {
|
||
try {
|
||
const content = readFileSync(QWEN_SETTINGS_FILE, "utf-8");
|
||
settings = JSON.parse(content);
|
||
log("✅ Loaded existing settings", "green");
|
||
} catch (err) {
|
||
log(`⚠️ Warning: Could not parse settings.json`, "yellow");
|
||
}
|
||
}
|
||
|
||
// Configure skills
|
||
if (!settings.skills) {
|
||
settings.skills = {};
|
||
}
|
||
|
||
if (!settings.skills.enabled) {
|
||
settings.skills.enabled = [];
|
||
}
|
||
|
||
const requiredSkills = [
|
||
"qwenclaw-integration",
|
||
"gui-automation",
|
||
"qwenbot-integration",
|
||
];
|
||
|
||
for (const skill of requiredSkills) {
|
||
if (!settings.skills.enabled.includes(skill)) {
|
||
settings.skills.enabled.push(skill);
|
||
log(` + Enabled skill: ${skill}`, "green");
|
||
}
|
||
}
|
||
|
||
settings.skills.default = "qwenclaw-integration";
|
||
log(" ✓ Set default skill: qwenclaw-integration", "green");
|
||
|
||
// Configure agents
|
||
if (!settings.agents) {
|
||
settings.agents = {};
|
||
}
|
||
|
||
settings.agents.default = "qwenclaw";
|
||
|
||
settings.agents.qwenclaw = {
|
||
name: "QwenClaw",
|
||
description: "Persistent AI assistant daemon with automation capabilities",
|
||
autoStart: true,
|
||
skills: ["qwenclaw-integration", "gui-automation", "qwenbot-integration"],
|
||
};
|
||
log(" ✓ Set default agent: qwenclaw", "green");
|
||
|
||
// Configure MCP servers
|
||
if (!settings.mcpServers) {
|
||
settings.mcpServers = {};
|
||
}
|
||
|
||
settings.mcpServers.qwenclaw = {
|
||
command: "bun",
|
||
args: ["run", "start", "--web"],
|
||
cwd: QWENCLAW_DIR,
|
||
env: {
|
||
QWENCLAW_AUTO_START: "true",
|
||
},
|
||
};
|
||
log(" ✓ Added MCP server: qwenclaw", "green");
|
||
|
||
// Save settings
|
||
try {
|
||
writeFile(QWEN_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
||
log("✅ Qwen Code settings saved", "green");
|
||
} catch (err) {
|
||
log(`❌ Error saving settings: ${err.message}`, "red");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create MCP config file
|
||
*/
|
||
function createMCPConfig() {
|
||
log("\n📝 Creating MCP configuration...", "cyan");
|
||
|
||
const mcpConfig = {
|
||
mcpServers: {
|
||
qwenclaw: {
|
||
command: "bun",
|
||
args: ["run", "start", "--web"],
|
||
cwd: QWENCLAW_DIR,
|
||
env: {
|
||
QWENCLAW_AUTO_START: "true",
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
try {
|
||
writeFile(QWEN_MCP_FILE, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
||
log("✅ MCP configuration created: " + QWEN_MCP_FILE, "green");
|
||
} catch (err) {
|
||
log(`⚠️ Could not create MCP config: ${err.message}`, "yellow");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Copy startup hook
|
||
*/
|
||
function installStartupHook() {
|
||
log("\n🪝 Installing startup hook...", "cyan");
|
||
|
||
const startupHookSrc = join(QWENCLAW_DIR, "hooks", "qwen-code-startup.js");
|
||
const startupHookDest = join(QWEN_DIR, "hooks", "startup.js");
|
||
|
||
if (existsSync(startupHookSrc)) {
|
||
try {
|
||
const hookDir = join(QWEN_DIR, "hooks");
|
||
if (!existsSync(hookDir)) {
|
||
mkdirSync(hookDir, { recursive: true });
|
||
}
|
||
|
||
const hookContent = readFileSync(startupHookSrc, "utf-8");
|
||
writeFile(startupHookDest, hookContent, "utf-8");
|
||
log("✅ Startup hook installed", "green");
|
||
} catch (err) {
|
||
log(`⚠️ Could not install startup hook: ${err.message}`, "yellow");
|
||
}
|
||
} else {
|
||
log("⚠️ Startup hook not found", "yellow");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Start QwenClaw daemon
|
||
*/
|
||
function startDaemon() {
|
||
log("\n🚀 Starting QwenClaw daemon...", "cyan");
|
||
|
||
const daemonProcess = spawn("bun", ["run", "start", "--web"], {
|
||
cwd: QWENCLAW_DIR,
|
||
detached: true,
|
||
stdio: "ignore",
|
||
windowsHide: true,
|
||
});
|
||
|
||
daemonProcess.unref();
|
||
|
||
// Wait for daemon to start
|
||
setTimeout(() => {
|
||
log("✅ QwenClaw daemon started", "green");
|
||
log(" Web Dashboard: http://127.0.0.1:4632", "cyan");
|
||
}, 3000);
|
||
}
|
||
|
||
/**
|
||
* Show completion message
|
||
*/
|
||
function showCompletion() {
|
||
log("\n" + "═".repeat(60), "cyan");
|
||
log(" ✅ QWENCLAW INSTALLATION COMPLETE!", "green");
|
||
log("═".repeat(60), "cyan");
|
||
|
||
log("\n📌 Configuration Summary:", "yellow");
|
||
log(" • QwenClaw Directory: " + QWENCLAW_DIR, "cyan");
|
||
log(" • Qwen Settings: " + QWEN_SETTINGS_FILE, "cyan");
|
||
log(" • MCP Config: " + QWEN_MCP_FILE, "cyan");
|
||
log(" • Default Agent: qwenclaw", "cyan");
|
||
log(" • Default Skill: qwenclaw-integration", "cyan");
|
||
|
||
log("\n🎯 Next Steps:", "yellow");
|
||
log(" 1. Restart Qwen Code", "cyan");
|
||
log(" 2. QwenClaw will auto-start automatically", "cyan");
|
||
log(" 3. Use /qwenclaw commands in Qwen Code chat", "cyan");
|
||
|
||
log("\n📚 Quick Reference:", "yellow");
|
||
log(" /qwenclaw start - Start daemon", "cyan");
|
||
log(" /qwenclaw status - Check status", "cyan");
|
||
log(" /qwenclaw send - Send message", "cyan");
|
||
log(" /qwenclaw skills - List skills", "cyan");
|
||
log(" /qwenclaw help - Show help", "cyan");
|
||
|
||
log("\n🌐 Web Dashboard: http://127.0.0.1:4632", "cyan");
|
||
log("\n📖 Documentation: " + QWENCLAW_DIR + "/README.md", "cyan");
|
||
log("\n");
|
||
}
|
||
|
||
/**
|
||
* Main installation function
|
||
*/
|
||
async function main() {
|
||
showBanner();
|
||
|
||
try {
|
||
checkInstallation();
|
||
ensureQwenDir();
|
||
updateSettings();
|
||
createMCPConfig();
|
||
installStartupHook();
|
||
startDaemon();
|
||
showCompletion();
|
||
} catch (err) {
|
||
log(`\n❌ Installation failed: ${err.message}`, "red");
|
||
log("\nPlease check the error above and try again.", "yellow");
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Run installer
|
||
main();
|