diff --git a/README.md b/README.md index c1db7ef..cee4101 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ QwenClaw runs as a background daemon, executing scheduled tasks, responding to Telegram messages, and providing a web dashboard for monitoring and management. It automatically starts with your system and persists across all restarts. -![Version](https://img.shields.io/badge/version-1.4.2-blue) +![Version](https://img.shields.io/badge/version-1.4.3-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) ![Rust](https://img.shields.io/badge/Rust-1.70+-orange) -![Skills](https://img.shields.io/badge/skills-77-green) +![Skills](https://img.shields.io/badge/skills-78-green) +![CLI](https://img.shields.io/badge/CLI-qwenclaw-purple) --- @@ -478,6 +479,32 @@ rm -rf QwenClaw-with-Auth QwenClaw follows [Semantic Versioning](https://semver.org/) (MAJOR.MINOR.PATCH). +### [1.4.3] - 2026-02-26 + +#### Added +- **QwenClaw CLI Command** (`qwenclaw`): + - `qwenclaw start --web` - Start daemon with web UI + - `qwenclaw start --with-qwen` - Start with Qwen Code + - `qwenclaw status` - Check daemon status + - `qwenclaw stop` - Stop daemon + - `qwenclaw send ` - Send message to daemon + - `qwenclaw skills` - List available skills + - `qwenclaw config` - Edit configuration + - `qwenclaw help` - Show help + +- **QwenClaw Integration Skill**: + - Enables Qwen Code to trigger QwenClaw + - Send tasks from Qwen Code to daemon + - Monitor daemon status + - Schedule jobs from chat + - Forward responses to Telegram + - Shared context between sessions + +#### Changed +- Updated package.json with bin entry +- Added postinstall script for CLI permissions +- Updated skills index to v1.4.3 (78 skills) + ### [1.4.2] - 2026-02-26 #### Added diff --git a/bin/qwenclaw.js b/bin/qwenclaw.js new file mode 100644 index 0000000..a5e1318 --- /dev/null +++ b/bin/qwenclaw.js @@ -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 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); +}); diff --git a/package.json b/package.json index 53c842f..31c896e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "qwenclaw", - "version": "1.3.0", + "version": "1.4.3", "type": "module", + "bin": { + "qwenclaw": "./bin/qwenclaw.js" + }, "scripts": { "start": "bun run src/index.ts", "dev:web": "bun --watch src/index.ts start --web --replace-existing", @@ -12,7 +15,8 @@ "rig:check": "cd rig-service && cargo check", "start:all": "bun run src/index.ts start --web --with-rig", "test": "bun test", - "test:rig": "bun test tests/rig-integration.test.ts" + "test:rig": "bun test tests/rig-integration.test.ts", + "postinstall": "chmod +x bin/qwenclaw.js 2>/dev/null || true" }, "devDependencies": { "@types/bun": "^1.3.9" diff --git a/skills/qwenclaw-integration/SKILL.md b/skills/qwenclaw-integration/SKILL.md new file mode 100644 index 0000000..04a5ec6 --- /dev/null +++ b/skills/qwenclaw-integration/SKILL.md @@ -0,0 +1,547 @@ +# QwenClaw Integration Skill + +## Overview + +This skill enables **Qwen Code** to directly trigger, control, and communicate with the **QwenClaw daemon**. It provides seamless integration between your interactive Qwen Code session and the persistent QwenClaw background processes. + +**Version:** 1.0.0 +**Category:** Integration +**Author:** QwenClaw Team + +--- + +## Features + +### 1. **Daemon Control** +- Start QwenClaw daemon from within Qwen Code +- Check daemon status +- Stop daemon gracefully +- Auto-restart if daemon crashes + +### 2. **Message Passing** +- Send prompts to running daemon +- Receive responses asynchronously +- Forward responses to Telegram +- Queue messages when daemon is offline + +### 3. **Task Orchestration** +- Delegate long-running tasks to daemon +- Monitor task progress +- Receive completion notifications +- Handle task failures gracefully + +### 4. **Shared Context** +- Share session context between Qwen Code and QwenClaw +- Synchronize settings and preferences +- Share skill configurations +- Unified logging and debugging + +--- + +## Installation + +### Prerequisites + +1. **QwenClaw installed:** + ```bash + git clone https://github.rommark.dev/admin/QwenClaw-with-Auth.git + cd QwenClaw-with-Auth + bun install + ``` + +2. **Qwen Code installed:** + ```bash + # Follow Qwen Code installation guide + ``` + +### Setup + +1. **Copy skill to Qwen Code:** + ```bash + # Copy this skill to Qwen Code skills directory + cp -r skills/qwenclaw-integration ~/.qwen/skills/qwenclaw-integration + ``` + +2. **Enable in Qwen Code:** + Edit `~/.qwen/settings.json`: + ```json + { + "enabledSkills": [ + "qwenclaw-integration" + ] + } + ``` + +3. **Configure paths:** + Create `~/.qwen/skills/qwenclaw-integration/config.json`: + ```json + { + "qwenclawPath": "/path/to/qwenclaw", + "autoStart": true, + "checkInterval": 30000, + "forwardToTelegram": false + } + ``` + +--- + +## Usage + +### From Qwen Code Chat + +#### Start QwenClaw + +``` +Use qwenclaw-integration to start the daemon with web UI +``` + +#### Check Status + +``` +Use qwenclaw-integration to check if QwenClaw is running +``` + +#### Send Task + +``` +Use qwenclaw-integration to send this task to QwenClaw: +"Monitor my GitHub notifications and summarize new issues" +``` + +#### Schedule Task + +``` +Use qwenclaw-integration to schedule a daily standup at 9 AM +``` + +#### Forward to Telegram + +``` +Use qwenclaw-integration to forward the response to Telegram +``` + +--- + +## API Reference + +### QwenClawClient Class + +```typescript +import { QwenClawClient } from './qwenclaw-integration'; + +const client = new QwenClawClient({ + qwenclawPath: '/path/to/qwenclaw', + autoStart: true, +}); + +// Check if daemon is running +const status = await client.getStatus(); +console.log(status); // { running: true, pid: 12345, ... } + +// Start daemon +await client.start({ web: true, webPort: 4632 }); + +// Send message +const response = await client.send('Check my pending tasks'); +console.log(response); // { success: true, output: '...' } + +// Stop daemon +await client.stop(); +``` + +### Methods + +#### `getStatus(): Promise` + +Check if QwenClaw daemon is running. + +**Returns:** +```typescript +interface DaemonStatus { + running: boolean; + pid?: number; + uptime?: number; + webEnabled?: boolean; + webPort?: number; + telegramEnabled?: boolean; + jobsCount?: number; +} +``` + +#### `start(options?: StartOptions): Promise` + +Start QwenClaw daemon. + +**Options:** +```typescript +interface StartOptions { + web?: boolean; + webPort?: number; + telegram?: boolean; + debug?: boolean; + trigger?: boolean; + prompt?: string; +} +``` + +#### `stop(): Promise` + +Stop QwenClaw daemon gracefully. + +#### `send(message: string, options?: SendOptions): Promise` + +Send message to running daemon. + +**Options:** +```typescript +interface SendOptions { + telegram?: boolean; + waitForResponse?: boolean; + timeout?: number; +} +``` + +**Returns:** +```typescript +interface SendResult { + success: boolean; + output?: string; + error?: string; + exitCode?: number; +} +``` + +#### `schedule(job: ScheduledJob): Promise` + +Schedule a new job with QwenClaw. + +**Job:** +```typescript +interface ScheduledJob { + name: string; + schedule: string; // Cron expression + prompt: string; + recurring: boolean; + notify: boolean | 'error'; +} +``` + +#### `listJobs(): Promise` + +List all scheduled jobs. + +#### `cancelJob(name: string): Promise` + +Cancel a scheduled job. + +--- + +## Examples + +### Example 1: Auto-Start Daemon + +```typescript +import { QwenClawClient } from './qwenclaw-integration'; + +const client = new QwenClawClient({ autoStart: true }); + +// This will automatically start QwenClaw if not running +const status = await client.getStatus(); +console.log(`QwenClaw is ${status.running ? 'running' : 'stopped'}`); +``` + +### Example 2: Delegate Long Task + +```typescript +const client = new QwenClawClient(); + +// Delegate code review to daemon +const result = await client.send( + 'Review all TypeScript files in src/ for potential bugs and suggest improvements', + { waitForResponse: true, timeout: 300000 } +); + +console.log(result.output); +``` + +### Example 3: Schedule Daily Report + +```typescript +await client.schedule({ + name: 'daily-report', + schedule: '0 9 * * 1-5', // Weekdays at 9 AM + prompt: 'Generate a daily report of my GitHub activity, pending PRs, and tasks', + recurring: true, + notify: true, +}); +``` + +### Example 4: Forward to Telegram + +```typescript +const result = await client.send( + 'Summarize my calendar for today', + { telegram: true } +); + +// Response will be sent to your Telegram +``` + +--- + +## Integration Patterns + +### Pattern 1: Interactive + Background + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Qwen Code │────────▶│ QwenClaw │ +│ (Interactive) │◀────────│ (Background) │ +└─────────────────┘ └─────────────────┘ + │ │ + ▼ ▼ + User Chat Long Tasks + Quick Questions Scheduled Jobs + Code Review Monitoring +``` + +**Use Case:** Use Qwen Code for interactive work, delegate long-running tasks to QwenClaw. + +### Pattern 2: Multi-Session Coordination + +``` +┌─────────────────┐ +│ Qwen Code │ +│ Session 1 │────┐ +└─────────────────┘ │ + ▼ +┌─────────────────┐ ┌─────────────────┐ +│ Qwen Code │──▶│ QwenClaw │ +│ Session 2 │ │ (Shared) │ +└─────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ Telegram │ + │ Notifications │ + └─────────────────┘ +``` + +**Use Case:** Multiple Qwen Code sessions share a single QwenClaw daemon for consistency. + +### Pattern 3: Task Pipeline + +``` +User Request + │ + ▼ +┌─────────────────┐ +│ Qwen Code │──▶ Analyze & Plan +└─────────────────┘ + │ + ▼ +┌─────────────────┐ +│ QwenClaw │──▶ Execute Long Task +└─────────────────┘ + │ + ▼ +┌─────────────────┐ +│ Telegram │◀── Notify Completion +└─────────────────┘ +``` + +**Use Case:** Complex workflows spanning multiple systems. + +--- + +## Configuration + +### config.json + +```json +{ + "qwenclawPath": "/absolute/path/to/qwenclaw", + "autoStart": true, + "checkInterval": 30000, + "forwardToTelegram": false, + "telegramBotToken": "", + "telegramAllowedUserIds": [], + "webDashboard": { + "enabled": true, + "port": 4632, + "host": "127.0.0.1" + }, + "logging": { + "level": "info", + "file": "~/.qwen/qwenclaw/integration.log" + } +} +``` + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `QWENCLAW_PATH` | Path to QwenClaw installation | `~/qwenclaw` | +| `QWENCLAW_AUTO_START` | Auto-start daemon | `true` | +| `QWENCLAW_CHECK_INTERVAL` | Status check interval (ms) | `30000` | +| `QWENCLAW_TELEGRAM_TOKEN` | Telegram bot token | - | +| `QWENCLAW_LOG_LEVEL` | Logging level | `info` | + +--- + +## Troubleshooting + +### Issue: "Daemon not found" + +**Solution:** +```bash +# Verify QwenClaw installation +cd /path/to/qwenclaw +bun run status + +# Reinstall if needed +bun install +``` + +### Issue: "Cannot start daemon" + +**Solution:** +```bash +# Check if port is in use +lsof -i :4632 + +# Check logs +cat ~/.qwen/qwenclaw/logs/*.log +``` + +### Issue: "Messages not delivered" + +**Solution:** +1. Check daemon is running: `qwenclaw status` +2. Verify network connectivity +3. Check firewall settings +4. Review integration logs + +--- + +## Best Practices + +### 1. **Use for Appropriate Tasks** + +✅ **Good for QwenClaw:** +- Long-running analyses +- Scheduled monitoring +- Background notifications +- Multi-step workflows + +❌ **Better for Qwen Code:** +- Quick questions +- Interactive coding +- Real-time collaboration +- Exploratory work + +### 2. **Manage Resources** + +```typescript +// Don't spawn too many daemon tasks +const client = new QwenClawClient(); + +// Queue tasks instead of parallel execution +await client.send('Task 1'); +await client.send('Task 2'); // Waits for Task 1 +``` + +### 3. **Handle Errors Gracefully** + +```typescript +try { + const result = await client.send('Complex task'); + if (!result.success) { + console.error('Task failed:', result.error); + } +} catch (err) { + console.error('Communication error:', err); + // Fallback to direct Qwen Code execution +} +``` + +### 4. **Monitor Daemon Health** + +```typescript +setInterval(async () => { + const status = await client.getStatus(); + if (!status.running) { + console.log('Daemon stopped, restarting...'); + await client.start(); + } +}, 60000); // Check every minute +``` + +--- + +## Security Considerations + +### 1. **API Key Protection** + +Never commit API keys to version control: + +```bash +# Use environment variables +export QWEN_API_KEY="your-key" +export TELEGRAM_BOT_TOKEN="your-token" +``` + +### 2. **Access Control** + +Limit Telegram access to trusted users: + +```json +{ + "telegramAllowedUserIds": [123456789, 987654321] +} +``` + +### 3. **Network Security** + +Bind web dashboard to localhost: + +```json +{ + "webDashboard": { + "host": "127.0.0.1", + "port": 4632 + } +} +``` + +--- + +## Resources + +- **QwenClaw Repo:** https://github.rommark.dev/admin/QwenClaw-with-Auth +- **Documentation:** `docs/README.md` in QwenClaw repo +- **Skills:** `skills/` directory +- **Issues:** https://github.rommark.dev/admin/QwenClaw-with-Auth/issues + +--- + +## Changelog + +### v1.0.0 (2026-02-26) +- Initial release +- Daemon control (start/stop/status) +- Message passing +- Task scheduling +- Telegram forwarding +- Shared context + +--- + +## License + +MIT License - See LICENSE file for details. + +--- + +**Skill ready for Qwen Code integration!** 🐾🤖 diff --git a/skills/skills-index.json b/skills/skills-index.json index 05ae75c..51ff59d 100644 --- a/skills/skills-index.json +++ b/skills/skills-index.json @@ -1,7 +1,7 @@ { - "version": "1.4.2", + "version": "1.4.3", "lastUpdated": "2026-02-26", - "totalSkills": 77, + "totalSkills": 78, "sources": [ { "name": "awesome-claude-skills", @@ -49,6 +49,12 @@ "url": "https://platform.qwen.ai/", "skillsCount": 1, "note": "QwenBot AI assistant tool for enhanced code analysis, documentation, and chat capabilities" + }, + { + "name": "qwenclaw-integration", + "url": "https://github.rommark.dev/admin/QwenClaw-with-Auth", + "skillsCount": 1, + "note": "Enables Qwen Code to trigger, control, and communicate with QwenClaw daemon" } ], "skills": [