#!/usr/bin/env node /** * QwenClaw CLI Command * * Usage: qwenclaw [command] [options] * * Commands: * start - Start QwenClaw daemon with Qwen Code * status - Check daemon status * stop - Stop daemon * send - Send message to running daemon * config - Configure QwenClaw * skills - List available skills * help - Show help */ import { spawn } from "child_process"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; import { existsSync, readFileSync } from "fs"; const __dirname = dirname(fileURLToPath(import.meta.url)); // Use absolute path to qwenclaw installation const QWENCLAW_DIR = process.env.QWENCLAW_DIR || join(__dirname, ".."); const QWENCLAW_START = join(QWENCLAW_DIR, "src", "index.ts"); // Colors for terminal output const colors = { reset: "\x1b[0m", cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", blue: "\x1b[34m", magenta: "\x1b[35m", }; function log(message: string, color = "reset") { console.log(`${colors[color as keyof typeof colors]}${message}${colors.reset}`); } function showBanner() { log("", "cyan"); log(" ╔═══════════════════════════════════════╗", "cyan"); log(" ║ 🐾 QWENCLAW - Your AI Assistant ║", "cyan"); log(" ║ Persistent daemon for Qwen Code ║", "cyan"); log(" ╚═══════════════════════════════════════╝", "cyan"); log("", "reset"); } function showHelp() { showBanner(); log("Usage: qwenclaw [command] [options]\n", "yellow"); log("Commands:", "green"); log(" start [options] Start QwenClaw daemon with Qwen Code"); log(" status Check if daemon is running"); log(" stop Stop the daemon"); log(" send Send message to running daemon"); log(" config Open configuration editor"); log(" skills List available skills"); log(" install-skill Install a new skill"); log(" help Show this help message"); log(""); log("Start Options:", "green"); log(" --web Open web dashboard"); log(" --web-port Custom web dashboard port"); log(" --telegram Enable Telegram bot"); log(" --debug Enable debug logging"); log(" --trigger Run startup trigger prompt"); log(" --prompt Run one-shot prompt"); log(""); log("Examples:", "green"); log(" qwenclaw start --web"); log(" qwenclaw send \"Check my pending tasks\""); log(" qwenclaw status"); log(" qwenclaw skills"); log(""); } async function startDaemon(args: string[]) { showBanner(); log("🚀 Starting QwenClaw daemon...\n", "green"); const bunArgs = ["run", QWENCLAW_START, "start", ...args]; // Check if Qwen Code should be launched const launchQwen = args.includes("--with-qwen") || args.includes("-q"); if (launchQwen) { log("📦 Launching Qwen Code with QwenClaw...\n", "cyan"); // Start Qwen Code with QwenClaw const qwenProcess = spawn("qwen", [], { stdio: "inherit", shell: true, }); qwenProcess.on("error", (err) => { log(`❌ Failed to start Qwen Code: ${err.message}`, "red"); }); } // Start QwenClaw daemon const daemonProcess = spawn("bun", bunArgs, { stdio: "inherit", shell: true, cwd: QWENCLAW_DIR, }); daemonProcess.on("error", (err) => { log(`❌ Failed to start daemon: ${err.message}`, "red"); process.exit(1); }); daemonProcess.on("exit", (code) => { if (code === 0) { log("\n✅ QwenClaw daemon stopped", "green"); } else { log(`\n❌ QwenClaw daemon exited with code ${code}`, "red"); } }); } async function checkStatus() { showBanner(); log("📊 Checking QwenClaw status...\n", "cyan"); const statusProcess = spawn("bun", ["run", QWENCLAW_START, "status"], { stdio: "inherit", shell: true, cwd: QWENCLAW_DIR, }); statusProcess.on("exit", (code) => { process.exit(code || 0); }); } async function stopDaemon() { showBanner(); log("🛑 Stopping QwenClaw daemon...\n", "yellow"); const stopProcess = spawn("bun", ["run", QWENCLAW_START, "stop"], { stdio: "inherit", shell: true, cwd: QWENCLAW_DIR, }); stopProcess.on("exit", (code) => { process.exit(code || 0); }); } async function sendMessage(message: string) { showBanner(); if (!message) { log("❌ Please provide a message to send", "red"); log("Usage: qwenclaw send ", "yellow"); process.exit(1); } log("📤 Sending message to daemon...\n", "cyan"); const sendProcess = spawn("bun", ["run", QWENCLAW_START, "send", ...message.split(" ")], { stdio: "inherit", shell: true, cwd: QWENCLAW_DIR, }); sendProcess.on("exit", (code) => { process.exit(code || 0); }); } async function listSkills() { showBanner(); log("📚 Available QwenClaw Skills\n", "cyan"); const skillsDir = join(QWENCLAW_DIR, "skills"); if (!existsSync(skillsDir)) { log("❌ Skills directory not found", "red"); process.exit(1); } // Read skills index const indexPath = join(skillsDir, "skills-index.json"); if (existsSync(indexPath)) { const index = JSON.parse(readFileSync(indexPath, "utf-8")); log(`Version: ${index.version} | Total Skills: ${index.totalSkills}\n`, "green"); // Group by source const sources = new Map(); for (const skill of index.skills) { const source = skill.source?.split("/")[0] || "other"; if (!sources.has(source)) { sources.set(source, []); } sources.get(source)!.push(skill); } for (const [source, skills] of sources.entries()) { log(`\n${source.toUpperCase()} (${skills.length}):`, "magenta"); for (const skill of skills.slice(0, 10)) { log(` • ${skill.name} - ${skill.description?.substring(0, 60) || "No description"}`, "blue"); } if (skills.length > 10) { log(` ... and ${skills.length - 10} more`, "yellow"); } } } else { log("❌ Skills index not found", "red"); } log("\n", "reset"); log("To use a skill:", "green"); log(" qwenclaw send \"Use to \"", "cyan"); log(""); } async function openConfig() { showBanner(); log("⚙️ Opening QwenClaw configuration...\n", "cyan"); const configPath = join(process.env.HOME || process.env.USERPROFILE || "", ".qwen", "qwenclaw", "settings.json"); if (!existsSync(configPath)) { log("❌ Configuration file not found. Run 'qwenclaw start' first to create it.", "red"); process.exit(1); } // Open with default editor const editor = process.env.EDITOR || "notepad"; const configProcess = spawn(editor, [configPath], { stdio: "inherit", shell: true, }); configProcess.on("exit", () => { log("\n✅ Configuration saved", "green"); }); } // Main command parser async function main() { const args = process.argv.slice(2); const command = args[0]; switch (command) { case "start": await startDaemon(args.slice(1)); break; case "status": await checkStatus(); break; case "stop": await stopDaemon(); break; case "send": await sendMessage(args.slice(1).join(" ")); break; case "config": await openConfig(); break; case "skills": await listSkills(); break; case "help": case "--help": case "-h": showHelp(); break; default: if (!command) { showHelp(); } else { log(`❌ Unknown command: ${command}`, "red"); log("Run 'qwenclaw help' for usage information", "yellow"); process.exit(1); } } } main().catch((err) => { log(`❌ Error: ${err.message}`, "red"); process.exit(1); });