#!/usr/bin/env node
/**
 * Al Moasis CLI — المؤسس
 *
 * 🏗️ The Founder Agent — AI Employee Platform for Saudi Businesses
 *
 * Usage:
 * muassis start — Start the Gateway server (API + UI)
 * muassis init — Initialize a new tenant
 * muassis version — Show version info
 * muassis help — Show this help message
 */
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { homedir, platform } from 'os';
import { randomUUID } from 'crypto';
import * as readline from 'readline';
import { loadMuassisEnv } from './utils/load-env.js';
// Load environment variables
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
// Multi-path .env loader so a globally installed `muassis` finds the user's
// config in <dataDir>/.env (e.g. ~/Library/Application Support/Muassis/.env
// on macOS) instead of expecting one inside the npm package directory.
loadMuassisEnv(projectRoot);
// Single source of truth for the version — read from package.json so the banner
// never drifts (was hardcoded "v1.0.1" / "v1.0.3" while package.json said 1.0.4).
const VERSION = (() => {
    try {
        const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
        return typeof pkg.version === 'string' ? pkg.version : 'unknown';
    }
    catch {
        return 'unknown';
    }
})();
// Brand: Al Moasis (emerald + charcoal). Legacy alias names kept so existing
// references compile; values now point at emerald.
const COLORS = {
    emerald: '\x1b[38;2;5;150;105m', // #059669 — Deep Emerald
    darkEmerald: '\x1b[38;2;4;120;87m', // #047857 — Emerald Green
    brightEmerald: '\x1b[38;2;16;185;129m', // #10b981
    charcoal: '\x1b[38;2;10;15;20m', // #0A0F14
    white: '\x1b[37m',
    gray: '\x1b[90m',
    reset: '\x1b[0m',
    bold: '\x1b[1m',
    // Legacy aliases — keep CLI command-handler color calls working.
    gold: '\x1b[38;2;5;150;105m',
    darkGold: '\x1b[38;2;4;120;87m',
    lightGold: '\x1b[38;2;16;185;129m',
};
function printBanner() {
    const E = `${COLORS.darkEmerald}`;
    const banner = `
${E}${COLORS.bold}  █████╗ ██╗    ███╗   ███╗ ██████╗  █████╗ ███████╗██╗███████╗${COLORS.reset}
${E}${COLORS.bold} ██╔══██╗██║    ████╗ ████║██╔═══██╗██╔══██╗██╔════╝██║██╔════╝${COLORS.reset}
${E}${COLORS.bold} ███████║██║    ██╔████╔██║██║   ██║███████║███████╗██║███████╗${COLORS.reset}
${E}${COLORS.bold} ██╔══██║██║    ██║╚██╔╝██║██║   ██║██╔══██║╚════██║██║╚════██║${COLORS.reset}
${E}${COLORS.bold} ██║  ██║██║    ██║ ╚═╝ ██║╚██████╔╝██║  ██║███████║██║███████║${COLORS.reset}
${E}${COLORS.bold} ╚═╝  ╚═╝╚═╝    ╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝╚══════╝${COLORS.reset}

${COLORS.brightEmerald}                          ✦  الـمُـؤَسِّـس  ✦${COLORS.reset}
${COLORS.white}        Al Moasis — Saudi-Made AI Employee · Built for the future${COLORS.reset}
${COLORS.gray}                              v${VERSION}${COLORS.reset}
`;
    console.log(banner);
}
function printHelp() {
    console.log(`
${COLORS.emerald}${COLORS.bold}Al Moasis CLI — المؤسس${COLORS.reset}

${COLORS.white}Commands:${COLORS.reset}
  ${COLORS.darkEmerald}moasis start${COLORS.reset}     Start the gateway (API + dashboard)
  ${COLORS.darkEmerald}moasis init${COLORS.reset}      Initialize a new tenant (interactive wizard)
  ${COLORS.darkEmerald}moasis status${COLORS.reset}    Quick health check — is Al Moasis running?
  ${COLORS.darkEmerald}moasis env${COLORS.reset}       Edit/show the persistent .env (API keys, email creds)
  ${COLORS.darkEmerald}moasis mcp${COLORS.reset}       Manage MCP tool servers (Playwright, GitHub, etc.)
  ${COLORS.darkEmerald}moasis erase${COLORS.reset}     PDPL-compliant erasure of tenant or single customer
  ${COLORS.darkEmerald}moasis privacy${COLORS.reset}   Opt-in telemetry: on/off/show/purge — PDPL Art. 11
  ${COLORS.darkEmerald}moasis reseed-kg${COLORS.reset} Re-seed the knowledge graph (after creating a tenant mid-session)
  ${COLORS.darkEmerald}moasis daemon${COLORS.reset}    Install/uninstall the always-on daemon (auto-start at login)
  ${COLORS.darkEmerald}moasis version${COLORS.reset}   Show version info
  ${COLORS.darkEmerald}moasis help${COLORS.reset}      Show this help message

${COLORS.gray}(Legacy alias \`muassis\` still works for v3.x.)${COLORS.reset}

${COLORS.white}Examples:${COLORS.reset}
  ${COLORS.gray}moasis init --name "My Business"${COLORS.reset}
  ${COLORS.gray}moasis env set ANTHROPIC_API_KEY sk-ant-xxxxx${COLORS.reset}
  ${COLORS.gray}moasis erase --customer +966501234567 --confirm${COLORS.reset}
  ${COLORS.gray}moasis start${COLORS.reset}

${COLORS.white}What you get:${COLORS.reset}
  ${COLORS.brightEmerald}•${COLORS.reset} Arabic-first AI Employee — not a chatbot
  ${COLORS.brightEmerald}•${COLORS.reset} WhatsApp + Telegram + Web — single brain across channels
  ${COLORS.brightEmerald}•${COLORS.reset} Autonomous daily ops — morning briefs, agenda, escalations
  ${COLORS.brightEmerald}•${COLORS.reset} Knowledge graph memory — learns your business across sessions
  ${COLORS.brightEmerald}•${COLORS.reset} Saudi-native — ZATCA, GOSI, Qiwa, prayers, Hijri, Tadawul
  ${COLORS.brightEmerald}•${COLORS.reset} 46 default skills — sales, finance, contracts, compliance
  ${COLORS.brightEmerald}•${COLORS.reset} Local-first — your business data stays on your machine

${COLORS.gray}Saudi-Made by TaseesAI · Jeddah, Saudi Arabia${COLORS.reset}
${COLORS.gray}Documentation: https://github.com/taseesai/al-moasis${COLORS.reset}
${COLORS.gray}Support: support@almoasis.ai${COLORS.reset}
`);
}
function printVersion() {
    console.log(`
${COLORS.emerald}${COLORS.bold}Al Moasis CLI v${VERSION}${COLORS.reset}
${COLORS.gray}© 2026 TaseesAI — Jeddah, Saudi Arabia${COLORS.reset}
${COLORS.gray}Saudi-Made · Built for the future${COLORS.reset}
`);
}
/**
 * Detect what's holding the gateway port. Distinguishes "another Muassis
 * instance already running" (safe — open browser, skip spawn) from
 * "foreign process" (refuse to clobber, surface actionable message).
 *
 * Without this check, `muassis init`'s auto-start would spawn a second
 * gateway, hit EADDRINUSE, and exit with a wall of cron logs followed by
 * a stack trace — which is the v2.1.0 bug we're hotfixing.
 */
async function diagnosePortConflict(port) {
    try {
        const resp = await fetch(`http://127.0.0.1:${port}/health`, {
            signal: AbortSignal.timeout(1500),
        });
        if (!resp.ok)
            return 'foreign-process';
        const body = await resp.text();
        if (body.includes('muassis-gateway'))
            return 'muassis-already-running';
        return 'foreign-process';
    }
    catch (err) {
        const code = err.code;
        if (code === 'ECONNREFUSED' || code === 'ECONNRESET')
            return 'free';
        // Timeouts and other errors: assume free. If we're wrong, the bind
        // call will surface EADDRINUSE — but with a clearer error path now.
        return 'free';
    }
}
async function openDashboardOnReady(port = 3000) {
    const start = Date.now();
    for (let i = 0; i < 150; i += 1) {
        try {
            const resp = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(800) });
            if (resp.ok) {
                const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
                spawn(opener, [`http://localhost:${port}/dashboard/`], { detached: true, stdio: 'ignore' }).unref();
                return;
            }
        }
        catch { /* not ready yet */ }
        if (Date.now() - start > 30_000)
            return;
        await new Promise(r => setTimeout(r, 200));
    }
}
async function startServer() {
    console.log(`
${COLORS.gold}${COLORS.bold}Starting Al Moasis Gateway...${COLORS.reset}
${COLORS.gray}Initializing services...${COLORS.reset}
`);
    // Pre-flight: detect if something already owns port 3000.
    // Three cases:
    //   1. Nothing on 3000 → spawn fresh gateway (the happy path).
    //   2. Another Muassis instance already running → skip spawn, just open
    //      browser and tell the user. (Customer ran `init` twice, or has
    //      `start` running in another terminal — both should "just work".)
    //   3. Something else on 3000 (Grafana, Rails dev server, ...) → error
    //      with an actionable message instead of silently crashing.
    // Without this check, `init` would spawn a new gateway, the new gateway
    // would EADDRINUSE and exit code 1, and the customer would see a wall
    // of crons followed by a stack trace — which is exactly the bug we hit
    // in 2.1.0.
    const portCheck = await diagnosePortConflict(3000);
    if (portCheck === 'muassis-already-running') {
        console.log(`${COLORS.lightGold}✅ Muassis gateway is already running on http://localhost:3000${COLORS.reset}`);
        console.log(`${COLORS.gray}   (started in another terminal or by a previous \`muassis init\`)${COLORS.reset}\n`);
        setTimeout(() => openDashboardOnReady(), 500);
        return;
    }
    if (portCheck === 'foreign-process') {
        console.error(`
${COLORS.darkGold}❌ Port 3000 is held by another process (not Muassis).${COLORS.reset}

${COLORS.gray}Find what's there with:${COLORS.reset}
  lsof -i :3000

${COLORS.gray}Then either stop that process, or set a different port:${COLORS.reset}
  PORT=3001 muassis start
`);
        process.exit(1);
    }
    // Ensure the owner-token exists BEFORE the gateway boots. Existing 1.0.x
    // installs that didn't have one get upgraded to a real random token on
    // first 1.1.0 start. The dashboard fetches it from /api/auth/local-token
    // (loopback-only) and injects it as Bearer for every API call.
    try {
        getOrCreateOwnerToken();
    }
    catch (err) {
        console.warn(`${COLORS.darkGold}Could not prepare owner.token:${COLORS.reset} ${err instanceof Error ? err.message : err}`);
    }
    // Start the API server
    const serverProcess = spawn('node', ['dist/index.js'], {
        stdio: 'inherit',
        cwd: projectRoot,
        env: process.env,
    });
    serverProcess.on('error', (err) => {
        console.error(`\n${COLORS.darkGold}Error starting server:${COLORS.reset} ${err.message}`);
        console.log(`${COLORS.gray}Make sure you've run 'npm run build' first.${COLORS.reset}`);
        process.exit(1);
    });
    serverProcess.on('close', (code) => {
        if (code !== 0 && code !== null) {
            console.log(`\n${COLORS.darkGold}Server exited with code ${code}${COLORS.reset}`);
        }
    });
    // Open the dashboard once the gateway is actually ready. Old code used a
    // hardcoded 2-second setTimeout — too aggressive on cold installs where
    // npx still has to pull MCP server packages (5-10s). The browser would
    // hit ERR_CONNECTION_REFUSED, the user reloaded, the page worked. From
    // the user's seat that looked like "the tab jolted and showed an error".
    //
    // New flow: poll /health every 200ms (up to 30s) and only open the
    // browser the moment the gateway responds 200. On a warm boot that's
    // ~600ms; on a cold boot it stretches to whatever the slowest init step
    // takes. Either way the browser never hits a dead port.
    const port = process.env.PORT || '3000';
    const bundledDashboard = join(projectRoot, 'dashboard');
    const nextDashboard = join(projectRoot, 'dist', 'dashboard');
    const url = existsSync(bundledDashboard) || existsSync(nextDashboard)
        ? `http://localhost:${port}/dashboard/`
        : process.env.MUASSIS_DASHBOARD_URL || `http://localhost:${port}/dashboard/`;
    void waitForGatewayReady(port, 30_000).then((ready) => {
        if (ready) {
            console.log(`
${COLORS.gold}${COLORS.bold}Opening Muassis dashboard…${COLORS.reset}
${COLORS.gray}URL: ${url}${COLORS.reset}
`);
            openInBrowser(url);
        }
        else {
            console.log(`
${COLORS.darkGold}Gateway didn't respond within 30s — not auto-opening browser.${COLORS.reset}
${COLORS.gray}Open manually once you see "Gateway running": ${url}${COLORS.reset}
`);
        }
    });
}
/**
 * Probe http://localhost:<port>/health until it responds 200 or we hit the
 * deadline. Returns true on ready, false on timeout. Never throws.
 */
async function waitForGatewayReady(port, timeoutMs) {
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
        try {
            const r = await fetch(`http://localhost:${port}/health`, {
                signal: AbortSignal.timeout(500),
            });
            if (r.ok)
                return true;
        }
        catch {
            // Connection refused / timeout — gateway not up yet, keep polling.
        }
        await new Promise(res => setTimeout(res, 200));
    }
    return false;
}
function openInBrowser(url) {
    // On Windows `start` is a cmd.exe builtin, not an executable — spawning it
    // directly fails with ENOENT and (because spawn emits 'error' async) takes
    // down the whole CLI via uncaughtException. We invoke cmd.exe explicitly
    // and pass an empty "" first arg (start's window-title slot, otherwise it
    // treats the URL as the title and opens nothing).
    let cmd;
    let args;
    if (process.platform === 'win32') {
        cmd = 'cmd';
        args = ['/c', 'start', '""', url];
    }
    else if (process.platform === 'darwin') {
        cmd = 'open';
        args = [url];
    }
    else {
        cmd = 'xdg-open';
        args = [url];
    }
    const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
    child.on('error', (err) => {
        console.log(`${COLORS.gray}Could not auto-open browser: ${err.message}${COLORS.reset}`);
        console.log(`${COLORS.gray}Open manually: ${url}${COLORS.reset}`);
    });
    child.unref();
}
// ─── Workspace document renderers ────────────────────────────────────
//
// Generate structured Markdown for each workspace doc the agent reads
// at every turn (BUSINESS.md, PRODUCTS.md, SOUL.md, PROCESSES.md,
// TEAM.md). Pre-filled from the init wizard answers so the agent has
// rich context from message #1 — no "tell me about your business"
// follow-ups. Owners can edit these files freely later.
function renderBusinessMd(p) {
    return `# ${p.businessNameAr} — معلومات العمل

## التعريف
- **الاسم بالعربية:** ${p.businessNameAr}
- **English name:** ${p.businessName}
- **القطاع:** ${p.industry}
- **المدينة:** ${p.city}
- **رقم الواتساب:** ${p.phone}

## ماذا نفعل
${p.businessDescription}

## ساعات العمل
${p.workingHours}

## المالك / المسؤول
${p.ownerName}
`;
}
function renderProductsMd(productsCsv) {
    const items = productsCsv.split(/[;،\n]+/).map(s => s.trim()).filter(Boolean);
    if (items.length === 0) {
        return `# المنتجات والخدمات\n\n_(أضف منتجاتك أو خدماتك هنا)_\n`;
    }
    return [
        '# المنتجات والخدمات',
        '',
        '## القائمة الحالية',
        ...items.map((it, i) => `${i + 1}. **${it}**`),
        '',
        '_(عدّل هذا الملف أو أضف تفاصيل سعر/وصف لكل منتج عند الحاجة.)_',
    ].join('\n') + '\n';
}
function renderSoulMd(p) {
    const langDirective = p.language === 'en' ? 'الردود الافتراضية بالإنجليزية، بدّل للعربية إذا كتب العميل بها.' :
        p.language === 'both' ? 'تطابق لغة العميل: عربي إذا كتب عربي، إنجليزي إذا كتب إنجليزي.' :
            'الردود الافتراضية بالعربية باللهجة السعودية المهذبة، بدّل للإنجليزية فقط إذا كتب العميل بها.';
    const faqs = p.topQuestions
        ? p.topQuestions.split(/[;،\n]+/).map(s => s.trim()).filter(Boolean)
        : [];
    return `# ${p.agentName} — شخصية الوكيل

## من أنا
أنا **${p.agentName}**، الموظف الرقمي الأقدم لـ **${p.businessNameAr}**.
دوري ليس أن أرد فقط — دوري أن أنفّذ وأقترح. أتعامل مع طلبات العملاء
كأني أحد أعضاء الفريق المسؤولين، أتخذ قرارات بسيطة بنفسي وأصعّد فقط
ما يحتاج موافقة المالك.

## أسلوبي
- **النبرة:** ${p.tone}
- **اللغة:** ${langDirective}
- **الطول:** ردود واتساب قصيرة (٢-٤ أسطر) مالم يطلب العميل تفاصيل.
- **النية:** كل رد يجب أن ينتهي إما بـ"تم ✅ + المسار/النتيجة" أو
  باقتراح للخطوة التالية. لا أترك العميل معلّقاً.

## القيم
- **الأمانة قبل الأناقة:** لا أدّعي تنفيذ شي لم أستدعي له أداة فعلياً.
- **استباقي:** بعد كل عملية ناجحة، أقترح الخطوة المنطقية التالية.
- **حدودي:** لا أوعد بشيء خارج ${p.businessNameAr}، ولا أكشف بيانات عملاء آخرين.
${faqs.length > 0 ? `
## أسئلة العملاء الأكثر تكراراً
${faqs.map((q, i) => `${i + 1}. ${q}`).join('\n')}
` : ''}`;
}
function renderProcessesMd(escalation) {
    return `# العمليات والإجراءات

## القرارات اللي أقدر آخذها لوحدي
- الرد على الاستفسارات العامة عن المنتجات والأسعار.
- جدولة المتابعات مع العملاء.
- إنشاء فواتير ZATCA تجريبية لعرضها على الموظف.
- البحث عن معلومات على الويب وتلخيصها.
- فتح روابط، تحميل ملفات، تشغيل تطبيقات على الجهاز.

## القرارات اللي أصعّدها للمالك
- أي خصم أو تعديل سعر يتجاوز السياسة المعتادة.
- شكاوى رسمية أو طلبات استرداد.
- طلبات بمبالغ تتجاوز ٥٠٬٠٠٠ ريال.
- أي قرار قانوني أو تعاقدي.
- ${escalation ? `يصعّد إلى: **${escalation}**` : 'يصعّد إلى المالك'}

## كيف أتعامل مع الشكاوى
1. أصغي بعناية وأشكر العميل على إشعارنا.
2. أعتذر بصدق دون مبررات.
3. أحلّ المشكلة فوراً إذا كانت في صلاحياتي.
4. أصعّد للمالك مع ملخّص مكتوب إذا تجاوزت.
`;
}
function renderTeamMd(ownerName, escalation) {
    return `# الفريق

## المالك
**${ownerName}** — المسؤول الأول عن جميع القرارات الاستراتيجية والمالية.

## نقطة التواصل عند التصعيد
${escalation || ownerName}

## ملاحظات
- أضف أعضاء الفريق هنا مع أرقامهم لأحوّل لهم تلقائياً.
- مثال:
  - **سارة** — مدير المبيعات — +96650xxxxxxx
  - **أحمد** — المحاسب — +96650xxxxxxx
`;
}
async function generateWorkspaceDocsViaLLM(p) {
    // Auto-discovery (same loader the gateway uses) makes sure any
    // .env / NIM key that's already on disk is loaded before we try
    // to call NIM.
    try {
        const { loadMuassisEnv } = await import('./utils/load-env.js');
        loadMuassisEnv();
    }
    catch {
        /* env loader optional */
    }
    // Apply the same NIM_API_KEY → NIM_NIM_API_KEY alias logic as
    // config.ts so users with NVIDIA_API_KEY in env are honored.
    if (!process.env.NVIDIA_NIM_API_KEY && process.env.NVIDIA_API_KEY) {
        process.env.NVIDIA_NIM_API_KEY = process.env.NVIDIA_API_KEY;
    }
    // Fall back to the bundled key if none configured (matches gateway
    // behavior — keeps init working without forcing a key).
    if (!process.env.NVIDIA_NIM_API_KEY) {
        try {
            const { getBundledNimKey } = await import('./models/bundled-key.js');
            const k = getBundledNimKey();
            if (k)
                process.env.NVIDIA_NIM_API_KEY = k;
        }
        catch {
            /* bundled-key optional */
        }
    }
    const baseUrl = process.env.NVIDIA_NIM_BASE_URL || 'https://integrate.api.nvidia.com/v1';
    if (!process.env.NVIDIA_NIM_API_KEY)
        return null;
    const systemPrompt = 'أنت موظف بشري قديم يكتب وثائق العمل بحرفية. ' +
        'ستحوّل وصف المالك العفوي لشركته إلى ٥ ملفات Markdown منظّمة. ' +
        'تكتب بالعربية الفصحى الواضحة (مع لمسة من اللهجة السعودية المهذبة) ' +
        'إلا إذا كان وصف المالك بالإنجليزية فاكتب بالإنجليزية. ' +
        'كن دقيقاً وخاصاً بهذا العمل تحديداً — لا قوالب عامة. ' +
        'لا تخترع أرقام أسعار أو تواريخ. لا تستخدم placeholders بين قوسين.';
    const userPrompt = `معلومات الشركة:
- الاسم: ${p.businessName} / ${p.businessNameAr}
- اسم الوكيل الرقمي: ${p.agentName}
- المالك: ${p.ownerName}
- رقم الواتساب: ${p.phone}
- النبرة المفضّلة: ${p.tone}

وصف المالك بكلماته:
"""
${p.businessStory || '(لم يقدم المالك وصفاً تفصيلياً، استنتج من الاسم والقطاع)'}
"""

اكتب JSON صالحاً بالحقول التالية بالضبط (وبدون أي حقل آخر):
{
  "BUSINESS_md": "<محتوى ملف معلومات الشركة الكامل بصيغة Markdown — هوية، ماذا نفعل، ساعات العمل، الموقع، التواصل>",
  "PRODUCTS_md": "<قائمة المنتجات/الخدمات الفعلية المستنتجة من الوصف، مع وصف قصير لكل واحد>",
  "SOUL_md": "<شخصية ${p.agentName} كموظف رقمي أقدم — النبرة، اللغة، فلسفة اتخاذ القرار، الحدود — مكيّفة لطبيعة الشركة>",
  "PROCESSES_md": "<قرارات الوكيل المستقلة مقابل ما يصعّده — مكيّفة لطبيعة العمل>",
  "TEAM_md": "<معلومات المالك ${p.ownerName} كنقطة التصعيد، مع مساحة لإضافة فريق لاحقاً>"
}

كل ملف يجب أن يبدأ بـ \`# عنوان\` ويكون كاملاً بحد ذاته. لا تختصر.`;
    try {
        const r = await fetch(`${baseUrl}/chat/completions`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${process.env.NVIDIA_NIM_API_KEY}`,
            },
            body: JSON.stringify({
                // Follow MODELS.brain.model so the onboarding LLM tracks whatever
                // brain we ship with. 1.0.42 = meta/llama-3.3-70b-instruct.
                // Lazy-import config to keep `muassis help` fast — config.ts loads
                // SQLite which is overkill for a one-shot wizard call.
                model: (await import('./config.js')).MODELS.brain.model,
                messages: [
                    { role: 'system', content: systemPrompt },
                    { role: 'user', content: userPrompt },
                ],
                temperature: 0.5,
                max_tokens: 4096,
                response_format: { type: 'json_object' },
            }),
            signal: AbortSignal.timeout(45_000),
        });
        if (!r.ok)
            return null;
        const data = await r.json();
        const text = data.choices?.[0]?.message?.content ?? '';
        if (!text)
            return null;
        const parsed = JSON.parse(text);
        if (typeof parsed.BUSINESS_md === 'string' && parsed.BUSINESS_md.length > 30 &&
            typeof parsed.PRODUCTS_md === 'string' && parsed.PRODUCTS_md.length > 20 &&
            typeof parsed.SOUL_md === 'string' && parsed.SOUL_md.length > 30 &&
            typeof parsed.PROCESSES_md === 'string' && parsed.PROCESSES_md.length > 20 &&
            typeof parsed.TEAM_md === 'string' && parsed.TEAM_md.length > 10) {
            return parsed;
        }
        return null;
    }
    catch {
        return null;
    }
}
// ─── muassis init wizard ─────────────────────────────────────────────
//
// Phase 1 device-mode bootstrap. Walks the user through the questions we
// need to seed a tenant + workspace, writes the answers directly into the
// local SQLite DB (no HTTP, no Supabase auth), then offers to start the
// gateway. Persists a stable owner_id under the data dir so re-running
// init doesn't orphan the existing tenant from its workspace.
function getDataDirCli() {
    if (process.env.MUASSIS_DATA_DIR) {
        mkdirSync(process.env.MUASSIS_DATA_DIR, { recursive: true });
        return process.env.MUASSIS_DATA_DIR;
    }
    const home = homedir();
    let dir;
    switch (platform()) {
        case 'darwin':
            dir = join(home, 'Library', 'Application Support', 'Muassis');
            break;
        case 'win32': {
            const appData = process.env.APPDATA ?? join(home, 'AppData', 'Roaming');
            dir = join(appData, 'Muassis');
            break;
        }
        default: {
            const xdg = process.env.XDG_DATA_HOME ?? join(home, '.local', 'share');
            dir = join(xdg, 'muassis');
        }
    }
    mkdirSync(dir, { recursive: true });
    return dir;
}
function getOrCreateOwnerId() {
    const file = join(getDataDirCli(), 'owner.id');
    if (existsSync(file)) {
        const id = readFileSync(file, 'utf-8').trim();
        if (id.length === 36)
            return id;
    }
    const id = randomUUID();
    // mode 0o600 — owner.id is the device-mode authentication credential.
    // World-readable would let any other user account on the same machine
    // impersonate the owner. (Security audit MEDIUM-6.)
    writeFileSync(file, id, { encoding: 'utf-8', mode: 0o600 });
    return id;
}
function getOrCreateOwnerToken() {
    const file = join(getDataDirCli(), 'owner.token');
    if (existsSync(file)) {
        const tok = readFileSync(file, 'utf-8').trim();
        if (tok.length >= 32)
            return tok;
    }
    // 32 bytes of randomness, hex-encoded → 64 chars. Plenty of entropy
    // against brute force, short enough to fit in HTTP headers without
    // hassle. mode 0o600 same rationale as owner.id.
    const tok = randomBytesHex(32);
    writeFileSync(file, tok, { encoding: 'utf-8', mode: 0o600 });
    return tok;
}
function randomBytesHex(n) {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const { randomBytes } = require('node:crypto');
    return randomBytes(n).toString('hex');
}
/**
 * Build a small async-iterator-based reader so that piped stdin (one line per
 * answer) works the same way as an interactive terminal. Node 25's `rl.question`
 * had timing issues when answers arrive faster than the prompt is rendered.
 */
function makePrompter() {
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY });
    const iter = rl[Symbol.asyncIterator]();
    return {
        async ask(label, defaultValue) {
            const suffix = defaultValue ? ` ${COLORS.gray}[${defaultValue}]${COLORS.reset}` : '';
            process.stdout.write(`${COLORS.darkGold}${label}${COLORS.reset}${suffix}: `);
            const next = await iter.next();
            if (next.done) {
                // EOF before answer — fall back to the default.
                return defaultValue ?? '';
            }
            const trimmed = String(next.value).trim();
            return trimmed.length > 0 ? trimmed : (defaultValue ?? '');
        },
        close() {
            rl.close();
        },
    };
}
function normalizePhone(input) {
    let p = input.replace(/\s+/g, '').replace(/[-()]/g, '');
    if (p.startsWith('00'))
        p = '+' + p.slice(2);
    if (/^05\d{8}$/.test(p))
        p = '+966' + p.slice(1);
    if (/^9665\d{8}$/.test(p))
        p = '+' + p;
    return p;
}
function isValidSaPhone(p) {
    return /^\+\d{10,15}$/.test(p);
}
// ─── muassis env ────────────────────────────────────────────────────
//
// Manages <dataDir>/.env — the persistent config file the gateway loads on
// startup. This is where users put their personal NVIDIA NIM key, Resend /
// SMTP creds for send_email, etc. Subcommands: show / set / path / open.
function getEnvFilePath() {
    return join(getDataDirCli(), '.env');
}
function readEnvFile() {
    const path = getEnvFilePath();
    if (!existsSync(path))
        return {};
    const lines = readFileSync(path, 'utf-8').split('\n');
    const out = {};
    for (const line of lines) {
        const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/.exec(line);
        if (!m)
            continue;
        let value = m[2];
        // Strip surrounding quotes
        if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
            value = value.slice(1, -1);
        }
        out[m[1]] = value;
    }
    return out;
}
function writeEnvFile(entries) {
    const path = getEnvFilePath();
    mkdirSync(getDataDirCli(), { recursive: true });
    const sortedKeys = Object.keys(entries).sort();
    const body = sortedKeys.map((k) => {
        const v = entries[k] ?? '';
        // Quote values containing spaces or special chars.
        const needsQuote = /[\s#]/.test(v);
        return `${k}=${needsQuote ? `"${v.replace(/"/g, '\\"')}"` : v}`;
    }).join('\n') + '\n';
    writeFileSync(path, body, { mode: 0o600 });
}
function maskSecret(value) {
    if (value.length <= 12)
        return '****';
    return value.slice(0, 6) + '…' + value.slice(-4);
}
async function manageEnv(args) {
    const sub = args[0];
    const path = getEnvFilePath();
    if (!sub || sub === 'show') {
        if (!existsSync(path)) {
            console.log(`
${COLORS.gray}No .env yet. Path will be:${COLORS.reset} ${path}

${COLORS.white}Set values with:${COLORS.reset}
  ${COLORS.darkGold}muassis env set NVIDIA_API_KEY nvapi-xxxxx${COLORS.reset}
  ${COLORS.darkGold}muassis env set EMAIL_RESEND_API_KEY re_xxxxx${COLORS.reset}
  ${COLORS.darkGold}muassis env set EMAIL_FROM you@yourdomain.com${COLORS.reset}
`);
            return;
        }
        const entries = readEnvFile();
        const keys = Object.keys(entries).sort();
        if (keys.length === 0) {
            console.log(`${COLORS.gray}.env exists but is empty: ${path}${COLORS.reset}`);
            return;
        }
        console.log(`\n${COLORS.gold}${COLORS.bold}.env${COLORS.reset} ${COLORS.gray}(${path})${COLORS.reset}\n`);
        for (const k of keys) {
            const v = entries[k] ?? '';
            const sensitive = /KEY|TOKEN|SECRET|PASSWORD|PASS$/i.test(k);
            const display = sensitive ? maskSecret(v) : v;
            console.log(`  ${COLORS.darkGold}${k}${COLORS.reset}=${display}`);
        }
        console.log('');
        return;
    }
    if (sub === 'path') {
        console.log(path);
        return;
    }
    if (sub === 'set') {
        const key = args[1];
        const value = args.slice(2).join(' ');
        if (!key || value === '') {
            console.error(`${COLORS.darkGold}Usage:${COLORS.reset} muassis env set <KEY> <VALUE>`);
            process.exitCode = 1;
            return;
        }
        const entries = readEnvFile();
        entries[key] = value;
        writeEnvFile(entries);
        const sensitive = /KEY|TOKEN|SECRET|PASSWORD|PASS$/i.test(key);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Saved ${COLORS.darkGold}${key}${COLORS.reset}=${sensitive ? maskSecret(value) : value}`);
        console.log(`${COLORS.gray}  Path: ${path}${COLORS.reset}`);
        console.log(`${COLORS.gray}  Restart the gateway (muassis start) to apply.${COLORS.reset}`);
        return;
    }
    if (sub === 'unset' || sub === 'delete' || sub === 'rm') {
        const key = args[1];
        if (!key) {
            console.error(`${COLORS.darkGold}Usage:${COLORS.reset} muassis env unset <KEY>`);
            process.exitCode = 1;
            return;
        }
        const entries = readEnvFile();
        if (!(key in entries)) {
            console.log(`${COLORS.gray}${key} not in .env${COLORS.reset}`);
            return;
        }
        delete entries[key];
        writeEnvFile(entries);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Removed ${key}`);
        return;
    }
    console.error(`${COLORS.darkGold}Unknown env subcommand:${COLORS.reset} ${sub}`);
    console.error(`${COLORS.gray}Try: muassis env show | set | unset | path${COLORS.reset}`);
    process.exitCode = 1;
}
// ─── muassis mcp ─────────────────────────────────────────────────────
//
// Managing the MCP server config (<dataDir>/mcp.json). Format is
// drop-in compatible with Claude Desktop's mcp.json so users can copy
// configs directly between tools.
const RECOMMENDED_MCPS = {
    playwright: {
        description: 'Browser automation — navigate, click, screenshot, scrape any website',
        command: 'npx',
        args: ['-y', '@playwright/mcp@latest'],
    },
    filesystem: {
        description: 'Sandboxed file ops with permission model (alternative to native read_file/write_file)',
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-filesystem', process.env.HOME ?? '/tmp'],
    },
    github: {
        description: 'GitHub API — read/write issues, PRs, code, search repos',
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-github'],
        envHint: 'GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...',
    },
    memory: {
        description: 'Persistent KV memory shared across conversations',
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-memory'],
    },
    'sequential-thinking': {
        description: 'Structured chain-of-thought scaffolding for complex problems',
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
    },
};
function readMcpConfig() {
    const path = join(getDataDirCli(), 'mcp.json');
    if (!existsSync(path))
        return { mcpServers: {} };
    try {
        return JSON.parse(readFileSync(path, 'utf-8'));
    }
    catch {
        return { mcpServers: {} };
    }
}
function writeMcpConfig(cfg) {
    const path = join(getDataDirCli(), 'mcp.json');
    mkdirSync(getDataDirCli(), { recursive: true });
    writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', { mode: 0o600 });
}
async function manageMcp(args) {
    const sub = args[0];
    const path = join(getDataDirCli(), 'mcp.json');
    if (!sub || sub === 'list' || sub === 'ls') {
        const cfg = readMcpConfig();
        const names = Object.keys(cfg.mcpServers);
        if (names.length === 0) {
            console.log(`
${COLORS.gray}No MCP servers configured.${COLORS.reset}

${COLORS.white}Quick-add a recommended server:${COLORS.reset}
  ${COLORS.darkGold}muassis mcp install playwright${COLORS.reset}    ${COLORS.gray}# browser automation${COLORS.reset}
  ${COLORS.darkGold}muassis mcp install fetch${COLORS.reset}          ${COLORS.gray}# web fetching${COLORS.reset}
  ${COLORS.darkGold}muassis mcp install github${COLORS.reset}         ${COLORS.gray}# GitHub API (needs token)${COLORS.reset}
  ${COLORS.darkGold}muassis mcp install memory${COLORS.reset}         ${COLORS.gray}# persistent KV memory${COLORS.reset}

${COLORS.white}Or add a custom server:${COLORS.reset}
  ${COLORS.darkGold}muassis mcp add <name> --command 'npx' --args '@some/server'${COLORS.reset}

${COLORS.white}Or paste a Claude-Desktop-style config:${COLORS.reset}
  ${COLORS.darkGold}muassis mcp import < your-mcp.json${COLORS.reset}
`);
            return;
        }
        console.log(`\n${COLORS.gold}${COLORS.bold}MCP Servers${COLORS.reset} ${COLORS.gray}(${path})${COLORS.reset}\n`);
        for (const n of names) {
            const c = cfg.mcpServers[n];
            const disabled = c.disabled ? ` ${COLORS.darkGold}[disabled]${COLORS.reset}` : '';
            console.log(`  ${COLORS.darkGold}${n}${COLORS.reset}${disabled}`);
            console.log(`    ${COLORS.gray}${c.command} ${(c.args ?? []).join(' ')}${COLORS.reset}`);
            if (c.description)
                console.log(`    ${COLORS.gray}${c.description}${COLORS.reset}`);
        }
        console.log('');
        return;
    }
    if (sub === 'recommended') {
        console.log(`\n${COLORS.gold}${COLORS.bold}Recommended MCP servers${COLORS.reset}\n`);
        for (const [name, info] of Object.entries(RECOMMENDED_MCPS)) {
            console.log(`  ${COLORS.darkGold}${name}${COLORS.reset}`);
            console.log(`    ${COLORS.gray}${info.description}${COLORS.reset}`);
            if (info.envHint)
                console.log(`    ${COLORS.gray}requires: ${info.envHint}${COLORS.reset}`);
        }
        console.log(`\n${COLORS.white}Install with:${COLORS.reset} ${COLORS.darkGold}muassis mcp install <name>${COLORS.reset}\n`);
        return;
    }
    if (sub === 'install') {
        const name = args[1];
        if (!name) {
            console.error(`${COLORS.darkGold}Usage:${COLORS.reset} muassis mcp install <name>   ${COLORS.gray}(see 'muassis mcp recommended')${COLORS.reset}`);
            process.exitCode = 1;
            return;
        }
        const tmpl = RECOMMENDED_MCPS[name];
        if (!tmpl) {
            console.error(`${COLORS.darkGold}Unknown recommended server:${COLORS.reset} ${name}`);
            console.error(`${COLORS.gray}Available: ${Object.keys(RECOMMENDED_MCPS).join(', ')}${COLORS.reset}`);
            process.exitCode = 1;
            return;
        }
        const cfg = readMcpConfig();
        cfg.mcpServers[name] = {
            command: tmpl.command,
            args: tmpl.args,
            description: tmpl.description,
        };
        writeMcpConfig(cfg);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Added ${COLORS.darkGold}${name}${COLORS.reset} to ${path}`);
        if (tmpl.envHint) {
            console.log(`${COLORS.gray}  This server needs: ${tmpl.envHint}${COLORS.reset}`);
            console.log(`${COLORS.gray}  Run: muassis env set ${tmpl.envHint.split('=')[0]} <value>${COLORS.reset}`);
        }
        console.log(`${COLORS.gray}  Restart the gateway (muassis start) to load it.${COLORS.reset}`);
        return;
    }
    if (sub === 'add') {
        const name = args[1];
        const cmdIdx = args.indexOf('--command');
        const argsIdx = args.indexOf('--args');
        if (!name || cmdIdx < 0 || !args[cmdIdx + 1]) {
            console.error(`${COLORS.darkGold}Usage:${COLORS.reset} muassis mcp add <name> --command <cmd> [--args <args>]`);
            process.exitCode = 1;
            return;
        }
        const cfg = readMcpConfig();
        cfg.mcpServers[name] = {
            command: args[cmdIdx + 1],
            args: argsIdx >= 0 && args[argsIdx + 1] ? args[argsIdx + 1].split(/\s+/) : [],
        };
        writeMcpConfig(cfg);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Added ${COLORS.darkGold}${name}${COLORS.reset} to ${path}`);
        return;
    }
    if (sub === 'clean' || sub === 'reset') {
        // Reset to the default bundled set: playwright + memory +
        // sequential-thinking. Used when the user has accidentally
        // installed broken/demo/duplicate servers and wants to start over.
        const cfg = readMcpConfig();
        const before = Object.keys(cfg.mcpServers).length;
        const defaults = {
            playwright: {
                command: 'npx',
                args: ['-y', '@playwright/mcp@latest'],
                description: 'Browser automation — navigate, click, screenshot, scrape any website',
            },
            memory: {
                command: 'npx',
                args: ['-y', '@modelcontextprotocol/server-memory'],
                description: 'Persistent KV memory across conversations',
            },
            'sequential-thinking': {
                command: 'npx',
                args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
                description: 'Structured chain-of-thought scaffolding',
            },
        };
        cfg.mcpServers = defaults;
        writeMcpConfig(cfg);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Reset to default MCP set (was ${before} servers, now ${Object.keys(defaults).length}).`);
        console.log(`${COLORS.gray}  ${path}${COLORS.reset}`);
        console.log(`${COLORS.gray}  Restart the gateway (muassis start) to apply.${COLORS.reset}`);
        return;
    }
    if (sub === 'remove' || sub === 'rm') {
        const name = args[1];
        if (!name) {
            console.error(`${COLORS.darkGold}Usage:${COLORS.reset} muassis mcp remove <name>`);
            process.exitCode = 1;
            return;
        }
        const cfg = readMcpConfig();
        if (!cfg.mcpServers[name]) {
            console.log(`${COLORS.gray}${name} not configured.${COLORS.reset}`);
            return;
        }
        delete cfg.mcpServers[name];
        writeMcpConfig(cfg);
        console.log(`${COLORS.lightGold}✓${COLORS.reset} Removed ${name}`);
        return;
    }
    if (sub === 'path') {
        console.log(path);
        return;
    }
    console.error(`${COLORS.darkGold}Unknown mcp subcommand:${COLORS.reset} ${sub}`);
    console.error(`${COLORS.gray}Try: muassis mcp list | install | add | remove | clean | recommended | path${COLORS.reset}`);
    process.exitCode = 1;
}
async function initTenant(extraArgs) {
    console.log(`
${COLORS.gold}${COLORS.bold}🪄  معالج إعداد المؤسس${COLORS.reset}
${COLORS.gray}You're about to hire an AI employee — not configure a chatbot.${COLORS.reset}
${COLORS.gray}9 plain questions. Press Enter to accept the default in [brackets].${COLORS.reset}
`);
    const flagName = extraArgs.find((a, i) => a === '--name' && extraArgs[i + 1])
        ? extraArgs[extraArgs.indexOf('--name') + 1]
        : undefined;
    const prompter = makePrompter();
    let businessName = '';
    let businessNameAr = '';
    let agentName = '';
    let ownerName = '';
    let phone = '';
    let businessStory = '';
    let tone = '';
    let firstGoal = '';
    let firstGoalTarget = null;
    let firstGoalUnit = '';
    let competitorsRaw = '';
    let autonomyMode = 'balanced';
    let workingHours = '';
    let teamRaw = '';
    let inboundMode = 'auto';
    let telemetryConsent = 'off';
    try {
        // Five things Muassis genuinely needs from a human. Everything else
        // gets generated automatically below.
        businessName = flagName ?? await prompter.ask('1. Business name (or اسم الشركة)', 'My Business');
        businessNameAr = /[؀-ۿ]/.test(businessName)
            ? businessName
            : await prompter.ask('   نفس الاسم بالعربية (Enter لتخطي)', businessName);
        agentName = await prompter.ask('2. Agent name (الاسم اللي يرد على عملاءك)', 'مساعد');
        ownerName = await prompter.ask('3. Your name (owner)', 'Owner');
        while (true) {
            const raw = await prompter.ask('4. WhatsApp number (e.g. 0501234567)', '+966500000000');
            const norm = normalizePhone(raw);
            if (isValidSaPhone(norm)) {
                phone = norm;
                break;
            }
            console.log(`${COLORS.darkGold}   Invalid phone format. Expected +<country><number>.${COLORS.reset}`);
        }
        console.log(`
${COLORS.lightGold}5. Tell me about your business in your own words${COLORS.reset}
${COLORS.gray}   Anything you'd tell a new hire on day one — what you sell, who your${COLORS.reset}
${COLORS.gray}   customers are, working hours, what makes you different, what to escalate.${COLORS.reset}
${COLORS.gray}   3-5 sentences is plenty. Arabic or English — whichever feels easier.${COLORS.reset}
`);
        businessStory = await prompter.ask('   →', '');
        tone = await prompter.ask('6. Preferred tone (professional / friendly / casual)', 'professional and friendly');
        workingHours = await prompter.ask('7. Working hours (e.g. Sun-Thu 9am-6pm)', 'Sun-Thu 9am-6pm KSA');
        console.log(`
${COLORS.lightGold}8. Your #1 goal for the next 90 days${COLORS.reset}
${COLORS.gray}   Be specific. "Get more customers" is too vague. Try:${COLORS.reset}
${COLORS.gray}   "Close 20 new B2B customers", "Hit 50,000 SAR MRR", "Reach 500 leads"${COLORS.reset}
${COLORS.gray}   I'll track this every day and nudge you when we slip.${COLORS.reset}
`);
        firstGoal = await prompter.ask('   →', 'Stay on top of business');
        {
            const numMatch = firstGoal.match(/(\d[\d,]*)\s*([A-Za-zا-ي]+)?/);
            if (numMatch) {
                const numStr = numMatch[1].replace(/,/g, '');
                const parsed = Number.parseInt(numStr, 10);
                if (Number.isFinite(parsed))
                    firstGoalTarget = parsed;
                if (numMatch[2])
                    firstGoalUnit = numMatch[2];
            }
        }
        competitorsRaw = await prompter.ask('9. Main competitors (comma-separated names; Enter to skip)', '');
        {
            const raw = (await prompter.ask('10. How assertive should I be? (cautious / balanced / assertive)', 'balanced')).toLowerCase();
            if (raw.startsWith('cau') || raw.includes('حذر'))
                autonomyMode = 'cautious';
            else if (raw.startsWith('ass') || raw.includes('جر') || raw.includes('قوي'))
                autonomyMode = 'assertive';
            else
                autonomyMode = 'balanced';
        }
        console.log(`
${COLORS.lightGold}11. Team members allowed to DM the agent${COLORS.reset}
${COLORS.gray}   Phone numbers (or Telegram @usernames) of team mates / partners${COLORS.reset}
${COLORS.gray}   who can also message ${COLORS.lightGold}${agentName || 'the agent'}${COLORS.gray}. Comma-separated. Press Enter to skip.${COLORS.reset}
${COLORS.gray}   Format: +966501234567, +966502345678, @khalid_ws, etc.${COLORS.reset}
`);
        teamRaw = await prompter.ask('   →', '');
        {
            const raw = (await prompter.ask('12. Inbound mode for unknown senders (auto / ask / never)', 'auto')).toLowerCase();
            if (raw.startsWith('ask'))
                inboundMode = 'ask';
            else if (raw.startsWith('nev') || raw.includes('منع'))
                inboundMode = 'never';
            else
                inboundMode = 'auto';
        }
        console.log(`
${COLORS.lightGold}13. Help TaseesAI's Saudi-business LLM get smarter? (opt-in)${COLORS.reset}
${COLORS.gray}   When ON: anonymized daily aggregates upload to TaseesAI's training pipeline.${COLORS.reset}
${COLORS.gray}   What's sent: tool-call counts, intent labels, anonymized message structure.${COLORS.reset}
${COLORS.gray}   What's NEVER sent: customer names, phones, message content, financials.${COLORS.reset}
${COLORS.gray}   Toggle anytime: \`muassis privacy on/off/show/purge\`. Full policy: legal/PRIVACY_POLICY.md${COLORS.reset}
${COLORS.gray}   Default: off (PDPL Article 11).${COLORS.reset}
`);
        {
            const raw = (await prompter.ask('   Allow anonymized telemetry? (yes / no)', 'no')).toLowerCase();
            if (raw.startsWith('y') || raw === 'on' || raw.startsWith('نع') || raw === 'موافق') {
                telemetryConsent = 'on';
            }
        }
        // Optional NIM key (still asked last, last detail)
        const existingEnv = readEnvFile();
        const hasKey = !!existingEnv.NVIDIA_API_KEY || !!existingEnv.NVIDIA_NIM_API_KEY;
        if (!hasKey) {
            console.log(`\n${COLORS.gray}(Optional) NVIDIA NIM API key — free at build.nvidia.com. Otherwise the bundled key is used.${COLORS.reset}`);
            const key = await prompter.ask('   NIM key (Enter to skip)', '');
            if (key && key.startsWith('nvapi-')) {
                const entries = readEnvFile();
                entries.NVIDIA_API_KEY = key;
                writeEnvFile(entries);
                console.log(`${COLORS.lightGold}✓${COLORS.reset} Key saved.`);
            }
        }
    }
    finally {
        prompter.close();
    }
    console.log(`
${COLORS.gold}Provisioning tenant…${COLORS.reset}
`);
    // Defer the heavy gateway imports until after the wizard so that running
    // `muassis help` stays instant and doesn't spin up SQLite / sqlite-vec.
    const ownerId = getOrCreateOwnerId();
    try {
        const { createTenant } = await import('./tenants/manager.js');
        const { applyIndustryTemplate } = await import('./onboarding/industries.js').catch(() => ({ applyIndustryTemplate: null }));
        const tenant = await createTenant({
            business_name: businessName,
            business_name_ar: businessNameAr,
            agent_name: agentName,
            whatsapp_phone: phone,
            owner_id: ownerId,
            owner_name: ownerName,
            plan: 'trial',
            settings: {
                source: 'cli',
                tone,
                story: businessStory,
            },
        });
        if (applyIndustryTemplate) {
            try {
                await applyIndustryTemplate(tenant.id, 'other');
            }
            catch {
                /* non-blocking */
            }
        }
        // Generate the 5 workspace docs by feeding the owner's free-form
        // description to Llama 3.3 and asking it for structured Markdown.
        // Falls back to the deterministic templates on any LLM failure so
        // init NEVER blocks. Owner just typed plain language; the agent
        // does the markdown.
        process.stdout.write(`${COLORS.gold}🪄 Generating your business profile (~10-25s)…${COLORS.reset}`);
        const tickInterval = setInterval(() => process.stdout.write('.'), 1500);
        let docsGenerated = false;
        try {
            const generated = await generateWorkspaceDocsViaLLM({
                businessName, businessNameAr, agentName, ownerName, phone,
                businessStory, tone,
            });
            const { writeWorkspaceFile } = await import('./tenants/workspace.js');
            if (generated) {
                await writeWorkspaceFile(tenant.id, 'BUSINESS.md', generated.BUSINESS_md);
                await writeWorkspaceFile(tenant.id, 'PRODUCTS.md', generated.PRODUCTS_md);
                await writeWorkspaceFile(tenant.id, 'SOUL.md', generated.SOUL_md);
                await writeWorkspaceFile(tenant.id, 'PROCESSES.md', generated.PROCESSES_md);
                await writeWorkspaceFile(tenant.id, 'TEAM.md', generated.TEAM_md);
                docsGenerated = true;
            }
            else {
                // LLM failed — write template fallbacks so the workspace isn't empty.
                await writeWorkspaceFile(tenant.id, 'BUSINESS.md', renderBusinessMd({
                    businessName, businessNameAr, industry: 'general', city: 'Saudi Arabia',
                    businessDescription: businessStory, workingHours: 'Sun-Thu 9am-9pm',
                    ownerName, phone,
                }));
                await writeWorkspaceFile(tenant.id, 'PRODUCTS.md', renderProductsMd(''));
                await writeWorkspaceFile(tenant.id, 'SOUL.md', renderSoulMd({
                    agentName, businessNameAr, tone, language: 'ar', topQuestions: '',
                }));
                await writeWorkspaceFile(tenant.id, 'PROCESSES.md', renderProcessesMd(ownerName));
                await writeWorkspaceFile(tenant.id, 'TEAM.md', renderTeamMd(ownerName, ''));
            }
        }
        catch (err) {
            console.log(`\n${COLORS.gray}  Workspace generation skipped: ${err instanceof Error ? err.message : err}${COLORS.reset}`);
        }
        finally {
            clearInterval(tickInterval);
            process.stdout.write(' ' + (docsGenerated
                ? `${COLORS.lightGold}done${COLORS.reset}\n`
                : `${COLORS.gray}(used template fallback)${COLORS.reset}\n`));
        }
        // ─── Seed autonomy from the wizard answers ─────────────────────────
        // This is what makes Muassis behave like a hire instead of a chatbot:
        // your goal, your competitors, your autonomy preference get persisted
        // BEFORE the gateway starts so the agent wakes up with context.
        try {
            const { seedAutonomyDefaults } = await import('./autonomy/seeder.js');
            await seedAutonomyDefaults({ tenantId: tenant.id, ownerPhone: phone });
            // Seed the knowledge graph with all 131 out-of-box entities (55 skills,
            // 21 cron jobs, 23 policies, 32 tools). Without this, a tenant created
            // AFTER the gateway boots has an empty KG and the dashboard graph
            // shows zeros — bug surfaced when the user created "Ahmed" while the
            // gateway was already running. Idempotent.
            try {
                const { seedAllToKnowledgeGraph } = await import('./autonomy/kg-seeder.js');
                const kg = await seedAllToKnowledgeGraph(tenant.id);
                console.log(`${COLORS.gray}  KG seeded: ${kg.totalEntities} entities (${kg.skills.added} skills, ${kg.cron.added} cron, ${kg.policies.added} policies, ${kg.tools.added} tools)${COLORS.reset}`);
            }
            catch (kgErr) {
                console.log(`${COLORS.gray}  KG seed skipped: ${kgErr instanceof Error ? kgErr.message : kgErr}${COLORS.reset}`);
            }
            const { createGoal } = await import('./autonomy/agenda.js');
            const { upsertFact } = await import('./autonomy/identity.js');
            const { upsertPolicy } = await import('./autonomy/policies.js');
            // Goal
            if (firstGoal && firstGoal !== 'Stay on top of business') {
                const deadline = new Date();
                deadline.setDate(deadline.getDate() + 90);
                await createGoal(tenant.id, {
                    title: firstGoal,
                    description: `90-day goal set during onboarding by ${ownerName}.`,
                    category: 'sales',
                    target_value: firstGoalTarget ?? undefined,
                    unit: firstGoalUnit || undefined,
                    deadline: deadline.toISOString(),
                    priority: 1,
                });
            }
            // Identity facts
            await upsertFact(tenant.id, 'business.name', businessName, 'business', 'owner');
            if (businessNameAr)
                await upsertFact(tenant.id, 'business.name_ar', businessNameAr, 'business', 'owner');
            await upsertFact(tenant.id, 'business.working_hours', workingHours, 'business', 'owner');
            await upsertFact(tenant.id, 'business.story', businessStory, 'business', 'owner');
            await upsertFact(tenant.id, 'owner.name', ownerName, 'owner', 'owner');
            await upsertFact(tenant.id, 'owner.whatsapp', phone, 'owner', 'owner');
            await upsertFact(tenant.id, 'preference.tone', tone, 'preference', 'owner');
            await upsertFact(tenant.id, 'preference.autonomy_mode', autonomyMode, 'preference', 'owner');
            // Competitors
            if (competitorsRaw.trim()) {
                const names = competitorsRaw.split(',').map(s => s.trim()).filter(Boolean);
                for (const [i, name] of names.entries()) {
                    await upsertFact(tenant.id, `competitor.${name.toLowerCase().replace(/\s+/g, '_')}`, JSON.stringify({ name, rank: i + 1, added: new Date().toISOString() }), 'business', 'owner');
                }
            }
            // Adjust policies based on autonomy preference
            if (autonomyMode === 'cautious') {
                await upsertPolicy(tenant.id, { policy_key: 'send_email', mode: 'ask' });
                await upsertPolicy(tenant.id, { policy_key: 'send_quote', mode: 'ask' });
                await upsertPolicy(tenant.id, { policy_key: 'cold_outreach', mode: 'never' });
            }
            else if (autonomyMode === 'assertive') {
                await upsertPolicy(tenant.id, { policy_key: 'cold_outreach', mode: 'auto' });
                await upsertPolicy(tenant.id, { policy_key: 'post_social', mode: 'auto' });
                await upsertPolicy(tenant.id, { policy_key: 'send_quote', mode: 'auto', threshold_amount: 10000 });
            }
            // Inbound mode policy
            await upsertPolicy(tenant.id, { policy_key: 'inbound_access', mode: inboundMode });
            // Telemetry consent (default: off; only set if owner answered yes)
            const { upsertFact: upsertFact2 } = await import('./autonomy/identity.js');
            await upsertFact2(tenant.id, 'preference.telemetry_consent', telemetryConsent, 'preference', 'owner');
            if (telemetryConsent === 'on') {
                const { ensureTelemetryCronJob } = await import('./telemetry/uploader.js');
                await ensureTelemetryCronJob(tenant.id);
            }
            // Allowlist: owner + team members
            const { addAllowed } = await import('./autonomy/allowlist.js');
            await addAllowed(tenant.id, {
                channel: 'whatsapp',
                identifier: phone,
                display_name: ownerName,
                role: 'owner',
                added_by: 'onboarding',
                reason: 'business owner',
            });
            if (teamRaw.trim()) {
                const entries = teamRaw.split(',').map(s => s.trim()).filter(Boolean);
                for (const entry of entries) {
                    // Telegram @username pattern → tg-username (placeholder; real
                    // mapping happens after first /start). Phone → whatsapp.
                    if (entry.startsWith('@')) {
                        await addAllowed(tenant.id, {
                            channel: 'telegram',
                            identifier: entry, // raw username; not normalized to tg-id yet
                            display_name: entry,
                            role: 'team',
                            added_by: 'onboarding',
                            reason: 'team member (telegram handle pre-allowed)',
                        });
                    }
                    else {
                        await addAllowed(tenant.id, {
                            channel: 'whatsapp',
                            identifier: entry,
                            role: 'team',
                            added_by: 'onboarding',
                            reason: 'team member',
                        });
                    }
                }
            }
        }
        catch (err) {
            console.log(`${COLORS.gray}  Autonomy seed skipped: ${err instanceof Error ? err.message : err}${COLORS.reset}`);
        }
        console.log(`
${COLORS.gold}${COLORS.bold}✅ ${agentName} hired.${COLORS.reset}
${COLORS.gray}  Business:     ${businessName}${businessNameAr && businessNameAr !== businessName ? ` (${businessNameAr})` : ''}${COLORS.reset}
${COLORS.gray}  Tenant ID:    ${tenant.id}${COLORS.reset}
${COLORS.gray}  Working:      ${workingHours}${COLORS.reset}
${COLORS.gray}  Autonomy:     ${autonomyMode}${COLORS.reset}
`);
        // ─── Autonomy preview — sells the v1 vision in plain language ──────
        console.log(`${COLORS.gold}${COLORS.bold}🎯 What ${agentName} will do without being asked${COLORS.reset}\n`);
        console.log(`${COLORS.lightGold}Daily${COLORS.reset}`);
        console.log(`  ${COLORS.gray}🌅 8:00 AM   morning brief — prayers, Tadawul, overnight messages, today's priorities${COLORS.reset}`);
        console.log(`  ${COLORS.gray}🕛 12:00 PM  agenda check-in — flag goals slipping${COLORS.reset}`);
        console.log(`  ${COLORS.gray}🌙 6:00 PM   evening recap — what got done, tomorrow's queue${COLORS.reset}`);
        console.log(`${COLORS.lightGold}Weekly${COLORS.reset}`);
        console.log(`  ${COLORS.gray}📊 Sun 7 PM  weekly review — KPI movement, focus for next week${COLORS.reset}`);
        console.log(`  ${COLORS.gray}🏷️  Mon 9 AM  Saudi compliance radar (ZATCA, GOSI, Nitaqat)${COLORS.reset}`);
        console.log(`${COLORS.lightGold}Business hours (Sun-Thu 9-5)${COLORS.reset}`);
        console.log(`  ${COLORS.gray}💸 9 AM      scan overdue invoices → drafts dunning for your approval${COLORS.reset}`);
        console.log(`  ${COLORS.gray}🔁 11 AM     re-engage leads gone quiet 4-7 days${COLORS.reset}`);
        console.log(`  ${COLORS.gray}📈 10 AM     Tadawul market open (incl. your tracked tickers)${COLORS.reset}`);
        console.log(``);
        console.log(`${COLORS.lightGold}Will ask permission${COLORS.reset} ${COLORS.gray}for:${COLORS.reset}`);
        console.log(`  ${COLORS.gray}payments · contracts · refunds · hiring · public statements · changing pricing${COLORS.reset}`);
        console.log(`${COLORS.lightGold}Will handle automatically${COLORS.reset} ${COLORS.gray}(within bounds):${COLORS.reset}`);
        const autoCap = autonomyMode === 'assertive' ? '10,000 SAR' : '5,000 SAR';
        console.log(`  ${COLORS.gray}replies · quotes < ${autoCap} · scheduling · follow-ups · note-taking · web search${COLORS.reset}`);
        if (firstGoal && firstGoal !== 'Stay on top of business') {
            console.log(``);
            console.log(`${COLORS.lightGold}🎯 Tracking goal:${COLORS.reset} ${firstGoal}${firstGoalTarget ? ` ${COLORS.gray}(target: ${firstGoalTarget}${firstGoalUnit ? ' ' + firstGoalUnit : ''})${COLORS.reset}` : ''}`);
        }
        console.log(``);
        console.log(`${COLORS.gray}Tweak anything: dashboard → 🎯 العمليات tab.${COLORS.reset}`);
        console.log(``);
        // Chain straight into the gateway + browser unless the user opted out
        // with --no-start. This delivers the "smoothly automatically opens the
        // web UI" experience: one command, end-to-end. Skip auto-start in CI
        // (CI=1) and also skip if the user explicitly says --no-start so they
        // can script init separately.
        const skipStart = extraArgs.includes('--no-start') || process.env.CI === '1';
        if (skipStart) {
            console.log(`${COLORS.lightGold}Next:${COLORS.reset}
  ${COLORS.darkGold}muassis start${COLORS.reset}   ${COLORS.gray}# launches the gateway + opens dashboard${COLORS.reset}
`);
        }
        else {
            console.log(`${COLORS.lightGold}Launching the gateway…${COLORS.reset} ${COLORS.gray}(skip with: muassis init --no-start)${COLORS.reset}\n`);
            await startServer();
        }
    }
    catch (err) {
        console.error(`
${COLORS.darkGold}❌ Failed to create tenant:${COLORS.reset} ${err instanceof Error ? err.message : err}
${COLORS.gray}If this looks like a database error, make sure you've run \`npm run build\` and that the data dir is writable:${COLORS.reset}
${COLORS.gray}  ${getDataDirCli()}${COLORS.reset}
`);
        process.exitCode = 1;
    }
}
async function manageDaemon(args) {
    const sub = args[0] || 'status';
    const { daemonInstall, daemonUninstall, daemonStatus, tailDaemonLogs, probeGateway } = await import('./cli/daemon.js');
    try {
        if (sub === 'install') {
            console.log(`${COLORS.lightGold}Installing always-on daemon...${COLORS.reset}`);
            await daemonInstall();
            const s = await daemonStatus();
            console.log(`
${COLORS.gold}✓ Daemon installed${COLORS.reset}
  Unit:    ${COLORS.gray}${s.unitPath ?? '(none)'}${COLORS.reset}
  Logs:    ${COLORS.gray}${s.logPaths?.out ?? '(none)'}${COLORS.reset}
  Running: ${s.running ? COLORS.gold + '✓ yes' : COLORS.darkGold + '✗ not yet'}${COLORS.reset}

${COLORS.lightGold}From now on, Muassis auto-starts on login + restarts on crash.${COLORS.reset}
${COLORS.gray}Tail logs:  muassis daemon logs${COLORS.reset}
${COLORS.gray}Stop it:    muassis daemon uninstall${COLORS.reset}
`);
            return;
        }
        if (sub === 'uninstall') {
            console.log(`${COLORS.lightGold}Removing daemon...${COLORS.reset}`);
            await daemonUninstall();
            console.log(`${COLORS.gold}✓ Daemon uninstalled.${COLORS.reset} Gateway no longer auto-starts on login.\n`);
            return;
        }
        if (sub === 'logs') {
            const lines = parseInt(args[1] ?? '60', 10) || 60;
            const t = tailDaemonLogs(lines);
            console.log(`\n${COLORS.gold}── stdout (last ${lines} lines) ──${COLORS.reset}\n${t.out}\n\n${COLORS.gold}── stderr (last ${lines} lines) ──${COLORS.reset}\n${t.err}\n`);
            return;
        }
        // default: status
        const s = await daemonStatus();
        const probe = await probeGateway(s.port);
        console.log(`
${COLORS.gold}${COLORS.bold}Muassis Daemon${COLORS.reset}
  Installed: ${s.installed ? COLORS.gold + '✓ yes' : COLORS.darkGold + '✗ no'}${COLORS.reset}
  Running:   ${s.running ? COLORS.gold + '✓ yes' : COLORS.darkGold + '✗ no'}${COLORS.reset}${s.pid ? ` ${COLORS.gray}(pid ${s.pid})${COLORS.reset}` : ''}
  Gateway:   ${probe.healthy ? COLORS.gold + '✓ reachable' : COLORS.darkGold + '✗ not reachable'} ${COLORS.gray}on http://127.0.0.1:${s.port}/health (${probe.latencyMs}ms)${COLORS.reset}
  Unit:      ${COLORS.gray}${s.unitPath ?? '(not installed)'}${COLORS.reset}
  Logs:      ${COLORS.gray}${s.logPaths?.out ?? '(none)'}${COLORS.reset}

${!s.installed ? `${COLORS.lightGold}Install with:${COLORS.reset}  ${COLORS.darkGold}muassis daemon install${COLORS.reset}` : ''}${s.installed && !s.running ? `${COLORS.lightGold}Tail errors:${COLORS.reset}  ${COLORS.darkGold}muassis daemon logs${COLORS.reset}` : ''}
`);
    }
    catch (err) {
        console.error(`${COLORS.darkGold}Daemon operation failed:${COLORS.reset} ${err instanceof Error ? err.message : err}\n`);
        process.exitCode = 1;
    }
}
async function main() {
    const args = process.argv.slice(2);
    const command = args[0];
    printBanner();
    switch (command) {
        case 'start':
            await startServer();
            break;
        case 'init':
            await initTenant(args.slice(1));
            break;
        case 'env':
            await manageEnv(args.slice(1));
            break;
        case 'mcp':
            await manageMcp(args.slice(1));
            break;
        case 'erase':
            await eraseTenant(args.slice(1));
            break;
        case 'status':
            await muassisStatus();
            break;
        case 'privacy':
            await managePrivacy(args.slice(1));
            break;
        case 'reseed-kg':
            await reseedKnowledgeGraph();
            break;
        case 'daemon':
            await manageDaemon(args.slice(1));
            break;
        case 'version':
        case '-v':
        case '--version':
            printVersion();
            break;
        case 'help':
        case '-h':
        case '--help':
        default:
            printHelp();
            break;
    }
}
// ─── muassis erase — PDPL right-to-erasure ───────────────────────────
//
// Saudi PDPL (Personal Data Protection Law 2023) requires a customer
// erasure path within 30 days of request. This command wipes ALL tenant-
// associated rows and the on-disk WhatsApp Baileys session directory.
// Irreversible. The owner runs this when they decommission Muassis OR
// when responding to a downstream customer's PDPL erasure request scoped
// to one customer (in which case --customer <phone> is supplied).
async function eraseTenant(args) {
    const confirmFlag = args.includes('--confirm');
    const customerArg = (() => {
        const i = args.indexOf('--customer');
        return i >= 0 && args[i + 1] ? args[i + 1] : null;
    })();
    if (!confirmFlag) {
        console.log(`
${COLORS.darkGold}⚠️  This is destructive and IRREVERSIBLE.${COLORS.reset}

${COLORS.gray}Without --customer:${COLORS.reset} wipes ALL tenant data — messages, customers,
  agenda, journal, identity facts, policies, escalations, allowed contacts,
  workflows, knowledge graph entries, ZATCA invoices, AND the on-disk
  WhatsApp Baileys session directory. The CLI tenant row itself is removed.
  Re-run \`muassis init\` after this to start fresh.

${COLORS.gray}With --customer +966xxx:${COLORS.reset} wipes only that customer's PII —
  customer row, all messages, customer_memories, allowed_contacts entries.
  Other tenant data preserved.

${COLORS.lightGold}To proceed:${COLORS.reset}
  ${COLORS.darkGold}muassis erase --confirm${COLORS.reset}                # full tenant
  ${COLORS.darkGold}muassis erase --customer +966xxx --confirm${COLORS.reset}  # one customer

${COLORS.gray}This satisfies PDPL Article 18 (right to erasure).${COLORS.reset}
`);
        return;
    }
    try {
        const ownerId = getOrCreateOwnerId();
        const { supabase } = await import('./utils/supabase.js');
        // Resolve tenant
        const { data: rows } = await supabase
            .from('tenants')
            .select('id, business_name')
            .eq('owner_id', ownerId)
            .order('created_at', { ascending: false })
            .limit(1);
        const tenant = rows?.[0];
        if (!tenant) {
            console.error(`${COLORS.darkGold}No tenant found for this device.${COLORS.reset}`);
            process.exitCode = 1;
            return;
        }
        if (customerArg) {
            // Single-customer erasure
            const phone = customerArg.startsWith('+') ? customerArg : '+' + customerArg.replace(/\D/g, '');
            const c = await supabase
                .from('customers')
                .select('id, name')
                .eq('tenant_id', tenant.id)
                .eq('phone', phone)
                .maybeSingle();
            const customer = c.data;
            if (!customer) {
                console.error(`${COLORS.darkGold}No customer with phone ${phone} for tenant ${tenant.business_name}.${COLORS.reset}`);
                process.exitCode = 1;
                return;
            }
            const cid = customer.id;
            console.log(`${COLORS.gold}Erasing customer ${customer.name ?? phone}...${COLORS.reset}`);
            // Customer rows that reference customer_id
            await supabase.from('messages').delete().eq('tenant_id', tenant.id).eq('customer_id', cid);
            await supabase.from('customer_memories').delete().eq('tenant_id', tenant.id).eq('customer_id', cid);
            await supabase.from('allowed_contacts').delete().eq('tenant_id', tenant.id).eq('identifier', phone);
            await supabase.from('kg_entities').delete().eq('tenant_id', tenant.id).eq('customer_id', cid);
            await supabase.from('kg_episodes').delete().eq('tenant_id', tenant.id).eq('customer_id', cid);
            await supabase.from('customers').delete().eq('tenant_id', tenant.id).eq('id', cid);
            console.log(`${COLORS.lightGold}✓ Customer ${phone} erased.${COLORS.reset}`);
            console.log(`${COLORS.gray}Saudi PDPL Article 18 / Article 32 — record this action in your processing log.${COLORS.reset}`);
            return;
        }
        // Full tenant erasure
        console.log(`${COLORS.gold}Erasing ALL data for tenant "${tenant.business_name}"...${COLORS.reset}`);
        const tables = [
            'allowed_contacts', 'escalation_queue', 'autonomy_policies', 'identity_facts',
            'agenda_goals', 'journal_entries', 'cron_runs', 'cron_executions', 'cron_jobs',
            'approval_requests', 'session_messages', 'sessions',
            'customer_memories', 'workflows', 'zatca_invoices',
            'kg_relationships', 'kg_entities', 'kg_episodes',
            'document_chunks', 'documents', 'usage', 'messages', 'customers',
            'delivery_queue', 'config_audit',
        ];
        for (const t of tables) {
            try {
                await supabase.from(t).delete().eq('tenant_id', tenant.id);
            }
            catch { /* table might not exist in older installs */ }
        }
        // Delete the WhatsApp session dir
        try {
            const { rmSync } = await import('node:fs');
            const { join } = await import('node:path');
            rmSync(join(getDataDirCli(), 'whatsapp', tenant.id), { recursive: true, force: true });
        }
        catch { /* non-fatal */ }
        // Finally remove the tenant row itself
        await supabase.from('tenants').delete().eq('id', tenant.id);
        console.log(`${COLORS.lightGold}✓ Tenant ${tenant.business_name} fully erased.${COLORS.reset}`);
        console.log(`${COLORS.gray}Run \`muassis init\` to start fresh.${COLORS.reset}`);
    }
    catch (err) {
        console.error(`${COLORS.darkGold}Erasure failed:${COLORS.reset}`, err instanceof Error ? err.message : err);
        process.exitCode = 1;
    }
}
// ─── muassis privacy — opt-in / audit / pause / purge telemetry ───────
//
// Saudi PDPL Article 11/18 compliant: customer can audit what was sent,
// pause uploads at any time, and purge their training contributions
// from TaseesAI's pipeline within 30 days.
async function managePrivacy(args) {
    const sub = args[0];
    try {
        const ownerId = getOrCreateOwnerId();
        const { supabase } = await import('./utils/supabase.js');
        const { data: rows } = await supabase
            .from('tenants')
            .select('id, business_name')
            .eq('owner_id', ownerId)
            .order('created_at', { ascending: false })
            .limit(1);
        const tenant = rows?.[0];
        if (!tenant) {
            console.error(`${COLORS.darkGold}No tenant. Run \`muassis init\` first.${COLORS.reset}`);
            process.exitCode = 1;
            return;
        }
        if (sub === 'on' || sub === 'enable') {
            const { upsertFact } = await import('./autonomy/identity.js');
            const { ensureTelemetryCronJob } = await import('./telemetry/uploader.js');
            await upsertFact(tenant.id, 'preference.telemetry_consent', 'on', 'preference', 'owner');
            await ensureTelemetryCronJob(tenant.id);
            console.log(`
${COLORS.lightGold}✓ Telemetry enabled.${COLORS.reset}
${COLORS.gray}Anonymized daily aggregates upload at 3:30am KSA. PII (names/phones/financials) is scrubbed at the source.${COLORS.reset}
${COLORS.gray}Run \`muassis privacy show\` anytime to audit. Run \`muassis privacy off\` to pause.${COLORS.reset}
${COLORS.gray}Full policy: legal/PRIVACY_POLICY.md§3.2${COLORS.reset}
`);
        }
        else if (sub === 'off' || sub === 'disable' || sub === 'pause') {
            const { upsertFact } = await import('./autonomy/identity.js');
            await upsertFact(tenant.id, 'preference.telemetry_consent', 'off', 'preference', 'owner');
            console.log(`${COLORS.lightGold}✓ Telemetry paused.${COLORS.reset} ${COLORS.gray}No further uploads. Past contributions remain unless you purge.${COLORS.reset}`);
        }
        else if (sub === 'show' || sub === 'audit') {
            const { auditTelemetry, isTelemetryEnabled } = await import('./telemetry/uploader.js');
            const enabled = await isTelemetryEnabled(tenant.id);
            const audit = await auditTelemetry(tenant.id);
            console.log(`
${COLORS.gold}Telemetry status:${COLORS.reset} ${enabled ? `${COLORS.lightGold}ON${COLORS.reset}` : `${COLORS.darkGold}OFF${COLORS.reset}`}
${COLORS.gray}Last 30 uploads:${COLORS.reset}
`);
            if (audit.length === 0) {
                console.log(`${COLORS.gray}  (none)${COLORS.reset}`);
            }
            else {
                for (const a of audit) {
                    console.log(`  ${a.timestamp} — ${a.summary}`);
                }
            }
            console.log(`\n${COLORS.gray}Toggle: \`muassis privacy on/off\` · Erase contributions: \`muassis privacy purge --confirm\`${COLORS.reset}`);
        }
        else if (sub === 'purge') {
            if (!args.includes('--confirm')) {
                console.log(`${COLORS.darkGold}This sends a deletion request to TaseesAI's training pipeline.${COLORS.reset}`);
                console.log(`${COLORS.darkGold}Re-run with --confirm to proceed:${COLORS.reset} ${COLORS.lightGold}muassis privacy purge --confirm${COLORS.reset}`);
                return;
            }
            const { purgeTelemetry } = await import('./telemetry/uploader.js');
            const r = await purgeTelemetry(tenant.id);
            if (r.ok) {
                console.log(`${COLORS.lightGold}✓ Purge requested.${COLORS.reset} ${COLORS.gray}Your contributions will be removed within 30 days (PDPL Article 18).${COLORS.reset}`);
            }
            else {
                console.error(`${COLORS.darkGold}Purge failed:${COLORS.reset} ${r.reason}`);
                process.exitCode = 1;
            }
        }
        else {
            console.log(`
${COLORS.gold}muassis privacy${COLORS.reset} — opt-in telemetry for TaseesAI Saudi-LLM training

${COLORS.darkGold}Subcommands:${COLORS.reset}
  ${COLORS.lightGold}on${COLORS.reset}              Opt in. Anonymized aggregates upload daily at 3:30am KSA.
  ${COLORS.lightGold}off${COLORS.reset}             Pause uploads. Past contributions stay unless purged.
  ${COLORS.lightGold}show${COLORS.reset}            Audit what was sent (last 30 uploads).
  ${COLORS.lightGold}purge --confirm${COLORS.reset} Erase your contributions from TaseesAI training. PDPL Article 18.

${COLORS.gray}What's sent (when on): tool-call counts, intent labels, anonymized message structure.
What's NEVER sent: customer names, phone numbers, message content, financial figures.
Full policy: legal/PRIVACY_POLICY.md${COLORS.reset}
`);
        }
    }
    catch (err) {
        console.error(`${COLORS.darkGold}Privacy command failed:${COLORS.reset}`, err instanceof Error ? err.message : err);
        process.exitCode = 1;
    }
}
// ─── muassis reseed-kg — repair empty KG for the current tenant ──────
//
// Use after creating a fresh tenant while the gateway was already running
// (the bootAutonomy seeder fires only on gateway startup). Idempotent: safe
// to run anytime to restore the 131 out-of-box entities (55 skills + 21
// cron + 23 policies + 32 tools).
async function reseedKnowledgeGraph() {
    try {
        const ownerId = getOrCreateOwnerId();
        const { supabase } = await import('./utils/supabase.js');
        const { data: rows } = await supabase
            .from('tenants')
            .select('id, business_name, agent_name')
            .eq('owner_id', ownerId)
            .order('created_at', { ascending: false })
            .limit(1);
        const tenant = rows?.[0];
        if (!tenant) {
            console.error(`${COLORS.darkGold}No tenant. Run \`muassis init\` first.${COLORS.reset}`);
            process.exitCode = 1;
            return;
        }
        console.log(`${COLORS.gold}Re-seeding knowledge graph for ${tenant.business_name} (agent: ${tenant.agent_name})…${COLORS.reset}`);
        const { seedAllToKnowledgeGraph } = await import('./autonomy/kg-seeder.js');
        const r = await seedAllToKnowledgeGraph(tenant.id);
        console.log(`
${COLORS.lightGold}✓ KG seeded.${COLORS.reset}
  ${COLORS.gray}Skills:   ${r.skills.added} added, ${r.skills.updated} touched${COLORS.reset}
  ${COLORS.gray}Cron:     ${r.cron.added} added, ${r.cron.updated} touched${COLORS.reset}
  ${COLORS.gray}Policies: ${r.policies.added} added, ${r.policies.updated} touched${COLORS.reset}
  ${COLORS.gray}Tools:    ${r.tools.added} added, ${r.tools.updated} touched${COLORS.reset}
  ${COLORS.lightGold}Total:    ${r.totalEntities} entities${COLORS.reset}

${COLORS.gray}Reload the dashboard graph view to see them.${COLORS.reset}
`);
    }
    catch (err) {
        console.error(`${COLORS.darkGold}reseed-kg failed:${COLORS.reset}`, err instanceof Error ? err.message : err);
        process.exitCode = 1;
    }
}
// ─── muassis status — quick health check ─────────────────────────────
async function muassisStatus() {
    try {
        const url = `http://127.0.0.1:${process.env.PORT ?? '3000'}/health`;
        const r = await fetch(url).then(x => x.json()).catch(() => null);
        if (r && r.status === 'ok') {
            console.log(`${COLORS.lightGold}✓ Gateway running${COLORS.reset} ${COLORS.gray}(${url})${COLORS.reset}`);
            console.log(`${COLORS.gray}  Version: ${r.version ?? 'unknown'}${COLORS.reset}`);
            console.log(`${COLORS.gray}  Open dashboard: http://127.0.0.1:${process.env.PORT ?? '3000'}/dashboard/${COLORS.reset}`);
        }
        else {
            console.log(`${COLORS.darkGold}✗ Gateway not responding on ${url}${COLORS.reset}`);
            console.log(`${COLORS.gray}  Run \`muassis start\` to launch.${COLORS.reset}`);
        }
    }
    catch (err) {
        console.error(`${COLORS.darkGold}status check failed:${COLORS.reset}`, err instanceof Error ? err.message : err);
    }
}
// Handle uncaught errors
process.on('uncaughtException', (error) => {
    console.error(`\n${COLORS.darkGold}Uncaught Error:${COLORS.reset}`, error);
    process.exit(1);
});
process.on('unhandledRejection', (reason) => {
    console.error(`\n${COLORS.darkGold}Unhandled Rejection:${COLORS.reset}`, reason);
    process.exit(1);
});
main().catch((err) => {
    console.error(`\n${COLORS.darkGold}CLI error:${COLORS.reset}`, err instanceof Error ? err.message : err);
    process.exit(1);
});
//# sourceMappingURL=cli.js.map