v1.4.3: Add qwenclaw CLI and QwenClaw Integration Skill
This commit is contained in:
302
bin/qwenclaw.js
Normal file
302
bin/qwenclaw.js
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/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));
|
||||
const 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 <message> Send message to running daemon");
|
||||
log(" config Open configuration editor");
|
||||
log(" skills List available skills");
|
||||
log(" install-skill <name> Install a new skill");
|
||||
log(" help Show this help message");
|
||||
log("");
|
||||
|
||||
log("Start Options:", "green");
|
||||
log(" --web Open web dashboard");
|
||||
log(" --web-port <port> Custom web dashboard port");
|
||||
log(" --telegram Enable Telegram bot");
|
||||
log(" --debug Enable debug logging");
|
||||
log(" --trigger Run startup trigger prompt");
|
||||
log(" --prompt <text> 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 <message>", "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<string, any[]>();
|
||||
|
||||
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 <skill-name> to <task>\"", "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);
|
||||
});
|
||||
Reference in New Issue
Block a user