Initial commit: QwenClaw persistent daemon for Qwen Code
This commit is contained in:
58
scripts/autostart.ps1
Normal file
58
scripts/autostart.ps1
Normal file
@@ -0,0 +1,58 @@
|
||||
# QwenClaw Auto-Start Script
|
||||
# This script ensures QwenClaw daemon is always running
|
||||
# Run at Windows startup via Task Scheduler
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
$QWENCLAW_DIR = Join-Path $env:USERPROFILE "qwenclaw"
|
||||
$QWEN_DIR = Join-Path $env:USERPROFILE ".qwen"
|
||||
$DAEMON_PID_FILE = Join-Path $QWEN_DIR "qwenclaw\daemon.pid"
|
||||
$LOG_FILE = Join-Path $QWEN_DIR "qwenclaw\autostart.log"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
"[$timestamp] $Message" | Out-File -FilePath $LOG_FILE -Append
|
||||
}
|
||||
|
||||
Write-Log "QwenClaw auto-start initiated"
|
||||
|
||||
# Check if daemon is already running
|
||||
if (Test-Path $DAEMON_PID_FILE) {
|
||||
$pidContent = Get-Content $DAEMON_PID_FILE -ErrorAction SilentlyContinue
|
||||
if ($pidContent) {
|
||||
try {
|
||||
$process = Get-Process -Id ([int]$pidContent) -ErrorAction SilentlyContinue
|
||||
if ($process) {
|
||||
Write-Log "QwenClaw daemon already running (PID: $pidContent)"
|
||||
exit 0
|
||||
}
|
||||
} catch {
|
||||
Write-Log "Stale PID file found, cleaning up..."
|
||||
Remove-Item $DAEMON_PID_FILE -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Wait for Qwen Code to be available (in case of system startup)
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
# Start daemon in background
|
||||
Write-Log "Starting QwenClaw daemon from: $QWENCLAW_DIR"
|
||||
|
||||
try {
|
||||
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$startInfo.FileName = "bun"
|
||||
$startInfo.Arguments = "run start --web"
|
||||
$startInfo.WorkingDirectory = $QWENCLAW_DIR
|
||||
$startInfo.UseShellExecute = $true
|
||||
$startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
||||
$startInfo.CreateNoWindow = $true
|
||||
|
||||
$process = [System.Diagnostics.Process]::Start($startInfo)
|
||||
Write-Log "QwenClaw daemon started (PID: $($process.Id))"
|
||||
} catch {
|
||||
Write-Log "Failed to start daemon: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-Log "QwenClaw auto-start completed"
|
||||
7
scripts/daemon-runner.bat
Normal file
7
scripts/daemon-runner.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
REM QwenClaw Daemon Runner
|
||||
REM Use this with NSSM or Windows Task Scheduler to run as a service
|
||||
|
||||
cd /d "%~dp0.."
|
||||
bun run start --web
|
||||
pause
|
||||
46
scripts/install-startup.ps1
Normal file
46
scripts/install-startup.ps1
Normal file
@@ -0,0 +1,46 @@
|
||||
# QwenClaw Windows Startup Installer (Startup Folder Method)
|
||||
# This method does NOT require admin privileges
|
||||
# Run this ONCE to register QwenClaw to start automatically with Windows
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$SCRIPT_NAME = "QwenClaw Daemon"
|
||||
$SCRIPT_PATH = Join-Path $PSScriptRoot "autostart.ps1"
|
||||
$LOG_DIR = Join-Path $env:USERPROFILE ".qwen\qwenclaw"
|
||||
$STARTUP_FOLDER = [Environment]::GetFolderPath("Startup")
|
||||
|
||||
Write-Host "QwenClaw Windows Startup Installer" -ForegroundColor Cyan
|
||||
Write-Host "===================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Using Windows Startup Folder method (no admin required)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Ensure log directory exists
|
||||
if (-not (Test-Path $LOG_DIR)) {
|
||||
New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null
|
||||
Write-Host "[OK] Created log directory: $LOG_DIR" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Create a shortcut in the Startup folder
|
||||
$shortcutPath = Join-Path $STARTUP_FOLDER "$SCRIPT_NAME.lnk"
|
||||
|
||||
try {
|
||||
$WScript = New-Object -ComObject WScript.Shell
|
||||
$shortcut = $WScript.CreateShortcut($shortcutPath)
|
||||
$shortcut.TargetPath = "PowerShell.exe"
|
||||
$shortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$SCRIPT_PATH`""
|
||||
$shortcut.WorkingDirectory = Split-Path $SCRIPT_PATH -Parent
|
||||
$shortcut.Description = "Automatically starts QwenClaw daemon when user logs on"
|
||||
$shortcut.IconLocation = "powershell.exe,0"
|
||||
$shortcut.Save()
|
||||
|
||||
Write-Host "[OK] Startup shortcut created: $shortcutPath" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "QwenClaw will now start automatically when you log in to Windows." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "To verify: Open Shell:Startup folder and look for '$SCRIPT_NAME'" -ForegroundColor Cyan
|
||||
Write-Host "To disable: Delete the shortcut from the Startup folder" -ForegroundColor Cyan
|
||||
} catch {
|
||||
Write-Host "[ERROR] Failed to create startup shortcut: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
75
scripts/qwen-startup-hook.js
Normal file
75
scripts/qwen-startup-hook.js
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* QwenClaw Startup Hook for Qwen Code
|
||||
* This script is called when Qwen Code starts
|
||||
* It ensures the QwenClaw daemon is running
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
const QWENCLAW_DIR = join(process.env.USERPROFILE || process.env.HOME || "", "qwenclaw");
|
||||
const QWEN_DIR = join(process.env.USERPROFILE || process.env.HOME || "", ".qwen");
|
||||
const PID_FILE = join(QWEN_DIR, "qwenclaw", "daemon.pid");
|
||||
const SETTINGS_FILE = join(QWEN_DIR, "qwenclaw", "settings.json");
|
||||
|
||||
function log(message) {
|
||||
console.log(`[QwenClaw] ${message}`);
|
||||
}
|
||||
|
||||
function isDaemonRunning() {
|
||||
if (!existsSync(PID_FILE)) return false;
|
||||
|
||||
try {
|
||||
const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
|
||||
if (isNaN(pid)) return false;
|
||||
|
||||
// Check if process exists
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function startDaemon() {
|
||||
if (isDaemonRunning()) {
|
||||
log("Daemon already running, skipping auto-start");
|
||||
return;
|
||||
}
|
||||
|
||||
log("Auto-starting QwenClaw daemon...");
|
||||
|
||||
try {
|
||||
const child = spawn("bun", ["run", "start", "--web"], {
|
||||
cwd: QWENCLAW_DIR,
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.unref();
|
||||
log(`Daemon started (PID: ${child.pid})`);
|
||||
} catch (err) {
|
||||
log(`Failed to start daemon: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if auto-start is enabled in settings
|
||||
function isAutoStartEnabled() {
|
||||
if (!existsSync(SETTINGS_FILE)) return true; // Default to enabled
|
||||
|
||||
try {
|
||||
const settings = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
||||
return settings.autoStart !== false; // Default to true
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
if (isAutoStartEnabled()) {
|
||||
// Delay slightly to not block Qwen Code startup
|
||||
setTimeout(startDaemon, 2000);
|
||||
}
|
||||
124
scripts/setup.ps1
Normal file
124
scripts/setup.ps1
Normal file
@@ -0,0 +1,124 @@
|
||||
# QwenClaw Setup Script
|
||||
# Run this ONCE to set up QwenClaw with persistent auto-start
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ____ _ __ _ _ " -ForegroundColor Cyan
|
||||
Write-Host " / ___|_ __ __ _ ___| | __ / _(_) | ___ " -ForegroundColor Cyan
|
||||
Write-Host "| | | '__/ _` |/ __| |/ /| |_| | |/ _ \ " -ForegroundColor Cyan
|
||||
Write-Host "| |___| | | (_| | (__| < | _| | | __/ " -ForegroundColor Cyan
|
||||
Write-Host " \____|_| \__,_|\___|_|\_\|_| |_|_|\___| " -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Persistent Daemon Setup" -ForegroundColor Cyan
|
||||
Write-Host "=======================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$QWENCLAW_DIR = Join-Path $env:USERPROFILE "qwenclaw"
|
||||
$QWEN_DIR = Join-Path $env:USERPROFILE ".qwen"
|
||||
|
||||
# Step 1: Check prerequisites
|
||||
Write-Host "[1/4] Checking prerequisites..." -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
$bunVersion = bun --version 2>&1
|
||||
Write-Host " Bun detected: v$bunVersion" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " [ERROR] Bun is not installed. Install from https://bun.sh" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 2: Install dependencies
|
||||
Write-Host ""
|
||||
Write-Host "[2/4] Installing dependencies..." -ForegroundColor Yellow
|
||||
Set-Location $QWENCLAW_DIR
|
||||
bun install
|
||||
|
||||
# Step 3: Create directories
|
||||
Write-Host ""
|
||||
Write-Host "[3/4] Creating directories..." -ForegroundColor Yellow
|
||||
$dirs = @(
|
||||
(Join-Path $QWEN_DIR "qwenclaw"),
|
||||
(Join-Path $QWEN_DIR "qwenclaw\jobs"),
|
||||
(Join-Path $QWEN_DIR "qwenclaw\logs"),
|
||||
(Join-Path $QWEN_DIR "qwenclaw\inbox")
|
||||
)
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
if (-not (Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
Write-Host " Created: $dir" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
# Step 4: Set up Windows startup
|
||||
Write-Host ""
|
||||
Write-Host "[4/4] Setting up Windows auto-start..." -ForegroundColor Yellow
|
||||
|
||||
$installScript = Join-Path $QWENCLAW_DIR "scripts\install-startup.ps1"
|
||||
& $installScript
|
||||
|
||||
# Create initial settings
|
||||
Write-Host ""
|
||||
Write-Host "Creating default settings..." -ForegroundColor Yellow
|
||||
|
||||
$settingsPath = Join-Path $QWEN_DIR "qwenclaw\settings.json"
|
||||
if (-not (Test-Path $settingsPath)) {
|
||||
$settings = @{
|
||||
model = ""
|
||||
api = ""
|
||||
autoStart = $true
|
||||
fallback = @{
|
||||
model = ""
|
||||
api = ""
|
||||
}
|
||||
timezone = "UTC"
|
||||
timezoneOffsetMinutes = 0
|
||||
heartbeat = @{
|
||||
enabled = $false
|
||||
interval = 15
|
||||
prompt = ""
|
||||
excludeWindows = @()
|
||||
}
|
||||
telegram = @{
|
||||
token = ""
|
||||
allowedUserIds = @()
|
||||
}
|
||||
security = @{
|
||||
level = "moderate"
|
||||
allowedTools = @()
|
||||
disallowedTools = @()
|
||||
}
|
||||
web = @{
|
||||
enabled = $true
|
||||
host = "127.0.0.1"
|
||||
port = 4632
|
||||
}
|
||||
}
|
||||
|
||||
$settings | ConvertTo-Json -Depth 10 | Out-File -FilePath $settingsPath -Encoding utf8
|
||||
Write-Host " Created: $settingsPath" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host " QwenClaw Setup Complete!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "What's configured:" -ForegroundColor Cyan
|
||||
Write-Host " [OK] Dependencies installed" -ForegroundColor Green
|
||||
Write-Host " [OK] Directories created" -ForegroundColor Green
|
||||
Write-Host " [OK] Windows auto-start enabled" -ForegroundColor Green
|
||||
Write-Host " [OK] Default settings created" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Quick Commands:" -ForegroundColor Cyan
|
||||
Write-Host " bun run start - Start daemon manually" -ForegroundColor White
|
||||
Write-Host " bun run status - Check daemon status" -ForegroundColor White
|
||||
Write-Host " bun run stop - Stop daemon" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Web Dashboard:" -ForegroundColor Cyan
|
||||
Write-Host " http://127.0.0.1:4632" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "The daemon will start automatically when you log in to Windows." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
37
scripts/uninstall-startup.ps1
Normal file
37
scripts/uninstall-startup.ps1
Normal file
@@ -0,0 +1,37 @@
|
||||
# QwenClaw Windows Startup Uninstaller
|
||||
# Run this to remove QwenClaw from Windows startup
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$TASK_NAME = "QwenClaw Daemon"
|
||||
$STARTUP_FOLDER = [Environment]::GetFolderPath("Startup")
|
||||
|
||||
Write-Host "QwenClaw Windows Startup Uninstaller" -ForegroundColor Cyan
|
||||
Write-Host "====================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Remove shortcut from Startup folder
|
||||
$shortcutPath = Join-Path $STARTUP_FOLDER "$TASK_NAME.lnk"
|
||||
|
||||
if (Test-Path $shortcutPath) {
|
||||
Remove-Item $shortcutPath -Force
|
||||
Write-Host "[OK] Startup shortcut removed: $shortcutPath" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] No startup shortcut found." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Also try to remove Task Scheduler task (if it exists from previous install)
|
||||
$existingTask = Get-ScheduledTask -TaskName $TASK_NAME -ErrorAction SilentlyContinue
|
||||
|
||||
if ($existingTask) {
|
||||
try {
|
||||
Unregister-ScheduledTask -TaskName $TASK_NAME -Confirm:$false
|
||||
Write-Host "[OK] Scheduled task '$TASK_NAME' removed successfully!" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "[INFO] Could not remove scheduled task (may require admin)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "QwenClaw will no longer start automatically with Windows." -ForegroundColor Yellow
|
||||
Write-Host "You can still start it manually with: bun run start" -ForegroundColor Cyan
|
||||
Reference in New Issue
Block a user