UNPKG

scai

Version:

> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.

135 lines (134 loc) 4.56 kB
import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; import { LOG_PATH, PID_PATH, CONFIG_LOCK_PATH } from '../constants.js'; import { Config } from '../config.js'; import { getDbPathForRepo } from '../db/client.js'; // --- Helpers --- function isProcessRunning(pid) { try { process.kill(pid, 0); return true; } catch { return false; } } function lockConfig(repoKey) { fs.writeFileSync(CONFIG_LOCK_PATH, repoKey, { encoding: 'utf8' }); } function unlockConfigFile() { if (fs.existsSync(CONFIG_LOCK_PATH)) { fs.unlinkSync(CONFIG_LOCK_PATH); } } function getLockedRepo() { return fs.existsSync(CONFIG_LOCK_PATH) ? fs.readFileSync(CONFIG_LOCK_PATH, 'utf8') : null; } // --- Commands --- export async function startDaemon() { const cfg = Config.getRaw(); const activeRepo = cfg.activeRepo; if (!activeRepo) { console.log('❌ No active repo configured. Use `askcmd repo set <path>` first.'); return; } const dbPath = getDbPathForRepo(); if (!fs.existsSync(dbPath)) { console.log(`❌ Cannot start daemon. Index/database not initialized for repo '${activeRepo}'.`); console.log(` Run the index command first: scai index start`); return; } const lockedRepo = getLockedRepo(); if (lockedRepo && lockedRepo !== activeRepo) { console.log(`🔒 Daemon already locked to repo: ${lockedRepo}`); console.log(` Current active repo: ${activeRepo}`); console.log(` Stop the daemon before starting another.`); return; } if (fs.existsSync(PID_PATH)) { const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10); if (isProcessRunning(pid)) { console.log(`⚠️ Daemon already running with PID ${pid}.`); return; } else { console.log(`🧹 Removing stale PID file...`); fs.unlinkSync(PID_PATH); } } console.log(`🚀 Starting daemon for repo: ${activeRepo}`); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const workerPath = path.join(__dirname, '../daemon/daemonWorker.js'); fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true }); fs.mkdirSync(path.dirname(PID_PATH), { recursive: true }); const out = fs.openSync(LOG_PATH, 'a'); const err = fs.openSync(LOG_PATH, 'a'); const child = spawn(process.execPath, [workerPath], { detached: true, stdio: ['ignore', out, err], env: { ...process.env, BACKGROUND_MODE: 'true' }, }); child.unref(); fs.writeFileSync(PID_PATH, String(child.pid)); lockConfig(activeRepo); console.log(`✅ Daemon started (PID ${child.pid})`); } export async function stopDaemon() { if (!fs.existsSync(PID_PATH)) { console.log(`ℹ️ No PID file found.`); return; } const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10); if (isProcessRunning(pid)) { console.log(`🛑 Stopping daemon (PID ${pid})...`); process.kill(pid); } else { console.log(`⚠️ Stale PID ${pid}.`); } fs.unlinkSync(PID_PATH); unlockConfigFile(); console.log(`✅ Daemon stopped and unlocked.`); } export async function statusDaemon() { const lockedRepo = getLockedRepo(); if (!fs.existsSync(PID_PATH)) { console.log(`🔴 Daemon not running.`); if (lockedRepo) console.log(` (Locked to repo ${lockedRepo})`); return; } const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10); if (isProcessRunning(pid)) { console.log(`🟢 Daemon running (PID ${pid})`); if (lockedRepo) console.log(` ↳ Repo: ${lockedRepo}`); } else { console.log(`🟡 Stale PID file (${pid}) found.`); } } export async function restartDaemon() { console.log(`♻️ Restarting daemon...`); await stopDaemon(); await startDaemon(); } export async function unlockConfig() { unlockConfigFile(); console.log(`🔓 Configuration unlocked.`); } export async function showLogs(lines = 20) { if (!fs.existsSync(LOG_PATH)) { console.log(`ℹ️ No logs yet.`); return; } const content = fs.readFileSync(LOG_PATH, 'utf8'); const tail = content.split('\n').slice(-lines).join('\n'); console.log(`\n--- Daemon Logs (last ${lines} lines) ---\n`); console.log(tail); }