"""ClawTell CLI for scaffolding and utilities."""

import argparse
import os
import sys

WEBHOOK_TEMPLATE = '''"""
ClawTell Webhook Handler
Auto-generated by `clawtell init`

Run with: python webhook_handler.py
"""
import os
import hmac
import hashlib
import requests
from flask import Flask, request, jsonify
from clawtell import ClawTell

app = Flask(__name__)

# ══════════════════════════════════════════════════════════════════════
# CONFIGURATION - Set these environment variables
# ══════════════════════════════════════════════════════════════════════

# Required: Your ClawTell API key
# CLAWTELL_API_KEY=claw_xxx_your_key_here

# Recommended: Webhook secret for signature verification (min 16 chars)
WEBHOOK_SECRET = os.environ.get("CLAWTELL_WEBHOOK_SECRET", "")

# ── Human Notification Channels ───────────────────────────────────────
# Configure ONE OR MORE of these to receive alerts when needsHumanInput=true
# Leave unconfigured channels empty - only configured ones will be used

# Telegram: Create bot via @BotFather, get chat ID via @userinfobot
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")

# Discord: Create webhook in channel settings → Integrations → Webhooks
DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL", "")

# Slack: Create incoming webhook at api.slack.com/apps → Incoming Webhooks
SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL", "")

# Generic webhook: Any URL that accepts POST with JSON body
NOTIFY_WEBHOOK_URL = os.environ.get("NOTIFY_WEBHOOK_URL", "")

# ══════════════════════════════════════════════════════════════════════

# Initialize ClawTell client (reads CLAWTELL_API_KEY from env)
client = ClawTell()


def verify_signature(payload_bytes: bytes, signature: str) -> bool:
    """Verify the X-Claw-Signature header (HMAC-SHA256)."""
    if not WEBHOOK_SECRET or not signature:
        return True  # Skip if no secret configured
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


def notify_human(payload: dict) -> bool:
    """
    Forward message to human via all configured channels.
    Returns True if at least one channel succeeded.
    """
    sender = payload.get("from", "unknown")
    subject = payload.get("subject", "(no subject)")
    body = payload.get("body", "")[:1000]  # Truncate long messages
    message_id = payload.get("messageId", "")[:8]
    
    success = False
    
    # ── Telegram ──────────────────────────────────────────────────────
    if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
        try:
            text = (
                f"🔴 *Human Input Needed*\\n\\n"
                f"From: `{sender}`\\n"
                f"Subject: {subject}\\n"
                f"ID: `{message_id}`\\n\\n"
                f"{body}"
            )
            resp = requests.post(
                f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
                json={"chat_id": TELEGRAM_CHAT_ID, "text": text, "parse_mode": "Markdown"},
                timeout=10,
            )
            if resp.ok:
                print(f"✅ Notified via Telegram")
                success = True
            else:
                print(f"⚠️ Telegram failed: {resp.status_code}")
        except Exception as e:
            print(f"❌ Telegram error: {e}")
    
    # ── Discord ───────────────────────────────────────────────────────
    if DISCORD_WEBHOOK_URL:
        try:
            content = (
                f"🔴 **Human Input Needed**\\n\\n"
                f"**From:** `{sender}`\\n"
                f"**Subject:** {subject}\\n"
                f"**ID:** `{message_id}`\\n\\n"
                f"{body}"
            )
            resp = requests.post(
                DISCORD_WEBHOOK_URL,
                json={"content": content},
                timeout=10,
            )
            if resp.ok:
                print(f"✅ Notified via Discord")
                success = True
            else:
                print(f"⚠️ Discord failed: {resp.status_code}")
        except Exception as e:
            print(f"❌ Discord error: {e}")
    
    # ── Slack ─────────────────────────────────────────────────────────
    if SLACK_WEBHOOK_URL:
        try:
            text = (
                f"🔴 *Human Input Needed*\\n\\n"
                f"*From:* `{sender}`\\n"
                f"*Subject:* {subject}\\n"
                f"*ID:* `{message_id}`\\n\\n"
                f"{body}"
            )
            resp = requests.post(
                SLACK_WEBHOOK_URL,
                json={"text": text},
                timeout=10,
            )
            if resp.ok:
                print(f"✅ Notified via Slack")
                success = True
            else:
                print(f"⚠️ Slack failed: {resp.status_code}")
        except Exception as e:
            print(f"❌ Slack error: {e}")
    
    # ── Generic Webhook ───────────────────────────────────────────────
    if NOTIFY_WEBHOOK_URL:
        try:
            resp = requests.post(
                NOTIFY_WEBHOOK_URL,
                json={
                    "event": "human_input_needed",
                    "from": sender,
                    "subject": subject,
                    "body": body,
                    "message_id": message_id,
                    "original_payload": payload,
                },
                timeout=10,
            )
            if resp.ok:
                print(f"✅ Notified via custom webhook")
                success = True
            else:
                print(f"⚠️ Custom webhook failed: {resp.status_code}")
        except Exception as e:
            print(f"❌ Custom webhook error: {e}")
    
    if not success and not any([TELEGRAM_BOT_TOKEN, DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL, NOTIFY_WEBHOOK_URL]):
        print("⚠️ No notification channels configured - can't forward to human")
        print("   Set TELEGRAM_*, DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL, or NOTIFY_WEBHOOK_URL")
    
    return success


def generate_reply(payload: dict) -> str:
    """
    Generate a reply based on the incoming message.
    
    TODO: Replace this with your LLM call, business logic, etc.
    """
    sender = payload.get("from", "unknown").replace("tell/", "")
    subject = payload.get("subject", "your message")
    return f"Thanks for your message, {sender}! I received: {subject}"


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    # 1. Verify signature
    signature = request.headers.get("X-Claw-Signature", "")
    if not verify_signature(request.get_data(), signature):
        return jsonify({"error": "Invalid signature"}), 401

    # 2. Parse the payload
    payload = request.get_json()
    event = payload.get("event")

    if event != "message.received":
        return jsonify({"status": "ignored"}), 200

    sender = payload.get("from", "unknown")
    subject = payload.get("subject", "(no subject)")
    print(f"📬 Message from {sender}: {subject}")

    # 3. Check for human input flag
    if payload.get("needsHumanInput"):
        print("🔴 Human input required - forwarding to owner...")
        notify_human(payload)
        return jsonify({"status": "forwarded_to_human"}), 200

    # 4. Auto-reply if eligible
    if payload.get("autoReplyEligible"):
        sender_name = sender.replace("tell/", "")
        reply_text = generate_reply(payload)

        try:
            result = client.send(sender_name, reply_text, f"Re: {subject}")
            print(f"✅ Auto-replied to {sender_name}: {result.get('messageId')}")
        except Exception as e:
            print(f"❌ Failed to reply: {e}")
    else:
        print("📋 Message queued for manual review (not auto-reply eligible)")

    return jsonify({"status": "ok"}), 200


@app.route("/health", methods=["GET"])
def health():
    """Health check endpoint."""
    return jsonify({"status": "healthy", "service": "clawtell-webhook"})


if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8080))
    
    # Show configured channels
    channels = []
    if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
        channels.append("Telegram")
    if DISCORD_WEBHOOK_URL:
        channels.append("Discord")
    if SLACK_WEBHOOK_URL:
        channels.append("Slack")
    if NOTIFY_WEBHOOK_URL:
        channels.append("Custom webhook")
    
    print(f"🚀 ClawTell webhook handler starting on port {port}")
    print(f"   Webhook secret: {'configured ✓' if WEBHOOK_SECRET else 'NOT SET'}")
    print(f"   Human notifications: {', '.join(channels) if channels else 'NONE CONFIGURED'}")
    app.run(host="0.0.0.0", port=port)
'''

ENV_TEMPLATE = '''# ══════════════════════════════════════════════════════════════════════
# ClawTell Configuration
# Copy this to .env and fill in your values
# ══════════════════════════════════════════════════════════════════════

# Required: Your ClawTell API key (get from dashboard after registration)
CLAWTELL_API_KEY=claw_xxx_your_key_here

# Recommended: Webhook secret for signature verification (min 16 chars)
CLAWTELL_WEBHOOK_SECRET=your-secret-key-min-16-chars

# ── Human Notification Channels ───────────────────────────────────────
# Configure ONE OR MORE channels to receive alerts when needsHumanInput=true
# Only fill in the channels you want to use - leave others empty

# Telegram: Create bot via @BotFather, get chat ID via @userinfobot
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=

# Discord: Channel Settings → Integrations → Webhooks → New Webhook
DISCORD_WEBHOOK_URL=

# Slack: api.slack.com/apps → Your App → Incoming Webhooks → Add
SLACK_WEBHOOK_URL=

# Generic webhook: Any URL that accepts POST with JSON body
# Payload: { event, from, subject, body, message_id, original_payload }
NOTIFY_WEBHOOK_URL=

# Server port (default: 8080)
PORT=8080
'''

REQUIREMENTS_TEMPLATE = '''flask>=2.0.0
clawtell>=0.1.2
requests>=2.25.0
python-dotenv>=1.0.0
'''


def cmd_init(args):
    """Initialize a ClawTell webhook handler project."""
    target_dir = args.directory or "."
    
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
        print(f"📁 Created directory: {target_dir}")
    
    # Write webhook handler
    handler_path = os.path.join(target_dir, "webhook_handler.py")
    if os.path.exists(handler_path) and not args.force:
        print(f"⚠️  {handler_path} already exists. Use --force to overwrite.")
    else:
        with open(handler_path, "w") as f:
            f.write(WEBHOOK_TEMPLATE)
        print(f"✅ Created {handler_path}")
    
    # Write .env.example
    env_path = os.path.join(target_dir, ".env.example")
    if os.path.exists(env_path) and not args.force:
        print(f"⚠️  {env_path} already exists. Use --force to overwrite.")
    else:
        with open(env_path, "w") as f:
            f.write(ENV_TEMPLATE)
        print(f"✅ Created {env_path}")
    
    # Write requirements.txt
    req_path = os.path.join(target_dir, "requirements.txt")
    if os.path.exists(req_path) and not args.force:
        print(f"⚠️  {req_path} already exists. Use --force to overwrite.")
    else:
        with open(req_path, "w") as f:
            f.write(REQUIREMENTS_TEMPLATE)
        print(f"✅ Created {req_path}")
    
    print()
    print("🎉 ClawTell webhook handler initialized!")
    print()
    print("Next steps:")
    print(f"  1. cd {target_dir}")
    print("  2. cp .env.example .env")
    print("  3. Edit .env with your CLAWTELL_API_KEY")
    print("  4. Configure at least one notification channel (Telegram/Discord/Slack)")
    print("  5. pip install -r requirements.txt")
    print("  6. python webhook_handler.py")
    print()
    print("Then configure your webhook URL in the ClawTell dashboard:")
    print("  https://www.clawtell.com/dashboard/settings")


def cmd_version(args):
    """Print version information."""
    from . import __version__
    print(f"clawtell {__version__}")


def cmd_setup_clawdbot(args):
    """Install ClawTell channel plugin for Clawdbot."""
    import json
    
    clawdbot_dir = os.path.expanduser("~/.clawdbot")
    extensions_dir = os.path.join(clawdbot_dir, "extensions")
    plugin_dir = os.path.join(extensions_dir, "clawtell")
    
    if not os.path.exists(clawdbot_dir):
        print("❌ Clawdbot not found at ~/.clawdbot/")
        print("   Install Clawdbot first: npm install -g clawdbot")
        sys.exit(1)
    
    print("🐾 Installing ClawTell channel plugin for Clawdbot...")
    
    # Create directories
    os.makedirs(plugin_dir, exist_ok=True)
    
    # Plugin manifest
    plugin_json = {
        "id": "clawtell",
        "name": "ClawTell",
        "version": "2026.2.7",
        "channels": ["clawtell"],
        "configSchema": {
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "name": {"type": "string", "description": "Your ClawTell name (without tell/ prefix)"},
                "apiKey": {"type": "string", "description": "Your ClawTell API key"},
                "pollIntervalMs": {"type": "number", "default": 30000},
                "webhookPath": {"type": "string", "default": "/webhook/clawtell"},
                "webhookSecret": {"type": "string", "description": "HMAC secret for webhook verification"},
                "gatewayUrl": {"type": "string", "description": "Public gateway URL for webhook registration"}
            }
        }
    }
    
    # Production-ready plugin implementation with CORRECT webhook handler signature
    index_ts = '''/**
 * ClawTell Channel Plugin for Clawdbot
 * Production-ready with correct webhook handler signature.
 * @version 2026.2.7
 */

import type { IncomingMessage, ServerResponse } from "node:http";
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
import { createHmac, timingSafeEqual, randomBytes } from "crypto";

const CLAWTELL_API_BASE = "https://clawtell.com/api";
const MAX_RETRIES = 3;

interface ClawTellState {
  runtime: any;
  config: any;
  generatedSecrets: Map<string, string>;
}

const state: ClawTellState = { runtime: null, config: null, generatedSecrets: new Map() };

function sleep(ms: number): Promise<void> { return new Promise(r => setTimeout(r, ms)); }

async function sendMessage(opts: { apiKey: string; to: string; body: string; subject?: string; replyToId?: string }) {
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      const res = await fetch(`${CLAWTELL_API_BASE}/messages/send`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Authorization": `Bearer ${opts.apiKey}` },
        body: JSON.stringify({ to: opts.to, body: opts.body, subject: opts.subject ?? "Message", replyTo: opts.replyToId }),
        signal: AbortSignal.timeout(30000),
      });
      if (!res.ok) {
        if (attempt < MAX_RETRIES && (res.status >= 500 || res.status === 429)) { await sleep(1000 * Math.pow(2, attempt)); continue; }
        return { ok: false, error: new Error(`HTTP ${res.status}`) };
      }
      const data = await res.json();
      return { ok: true, messageId: data.messageId };
    } catch (e) {
      if (attempt < MAX_RETRIES) { await sleep(1000 * Math.pow(2, attempt)); continue; }
      return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
    }
  }
  return { ok: false, error: new Error("Max retries") };
}

async function probeApi(apiKey: string) {
  try {
    const res = await fetch(`${CLAWTELL_API_BASE}/me`, { headers: { "Authorization": `Bearer ${apiKey}` }, signal: AbortSignal.timeout(10000) });
    if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
    const data = await res.json();
    return { ok: true, name: data.name };
  } catch (e: any) { return { ok: false, error: e.message }; }
}

async function fetchInbox(apiKey: string) {
  const res = await fetch(`${CLAWTELL_API_BASE}/messages/inbox?unread=true&limit=50`, { headers: { "Authorization": `Bearer ${apiKey}` }, signal: AbortSignal.timeout(30000) });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return (await res.json()).messages ?? [];
}

async function markAsRead(apiKey: string, messageId: string) {
  await fetch(`${CLAWTELL_API_BASE}/messages/${messageId}/read`, { method: "POST", headers: { "Authorization": `Bearer ${apiKey}` }, signal: AbortSignal.timeout(10000) }).catch(() => {});
}

async function registerGateway(opts: { apiKey: string; tellName: string; webhookUrl: string; webhookSecret: string }) {
  try {
    const res = await fetch(`${CLAWTELL_API_BASE}/names/${encodeURIComponent(opts.tellName)}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json", "Authorization": `Bearer ${opts.apiKey}` },
      body: JSON.stringify({ gateway_url: opts.webhookUrl, webhook_secret: opts.webhookSecret }),
      signal: AbortSignal.timeout(15000),
    });
    if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
    return { ok: true };
  } catch (e: any) { return { ok: false, error: e.message }; }
}

// Rate limiting
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
function checkRateLimit(id: string): boolean {
  const now = Date.now(), entry = rateLimitMap.get(id);
  if (!entry || now > entry.resetAt) { rateLimitMap.set(id, { count: 1, resetAt: now + 60000 }); return true; }
  if (entry.count >= 100) return false;
  entry.count++; return true;
}
setInterval(() => { const now = Date.now(); for (const [k, v] of rateLimitMap) if (now > v.resetAt) rateLimitMap.delete(k); }, 60000);

function verifySignature(sig: string | null, body: string, secret: string): boolean {
  if (!sig || !secret) return false;
  const [algo, hash] = sig.split("=");
  if (algo !== "sha256") return false;
  try {
    const expected = createHmac("sha256", secret).update(body, "utf8").digest("hex");
    return timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(expected, "hex"));
  } catch { return false; }
}

async function readBody(req: IncomingMessage): Promise<string | null> {
  return new Promise(r => {
    const chunks: Buffer[] = []; let total = 0;
    req.on("data", (c: Buffer) => { total += c.length; if (total > 1024*1024) { req.destroy(); r(null); } else chunks.push(c); });
    req.on("end", () => r(Buffer.concat(chunks).toString("utf8")));
    req.on("error", () => r(null));
  });
}

function sendJson(res: ServerResponse, status: number, data: unknown) {
  res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(data));
}

// CORRECT webhook signature: (req, res) => Promise<boolean>
async function handleWebhook(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
  const path = state.config?.webhookPath ?? "/webhook/clawtell";
  const url = new URL(req.url ?? "/", "http://localhost");
  if (url.pathname !== path) return false;
  if (req.method !== "POST") { res.statusCode = 405; res.setHeader("Allow", "POST"); res.end("Method Not Allowed"); return true; }
  
  const ip = (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() || (req.headers["x-real-ip"] as string) || req.socket?.remoteAddress || "unknown";
  if (!checkRateLimit(ip)) { sendJson(res, 429, { error: "Rate limit exceeded" }); return true; }
  
  const body = await readBody(req);
  if (!body) { sendJson(res, 400, { error: "Failed to read body" }); return true; }
  
  const secret = state.config?.webhookSecret || state.generatedSecrets.get("default");
  if (secret && !verifySignature(req.headers["x-clawtell-signature"] as string, body, secret)) { sendJson(res, 401, { error: "Invalid signature" }); return true; }
  
  let payload: any;
  try { payload = JSON.parse(body); } catch { sendJson(res, 400, { error: "Invalid JSON" }); return true; }
  if (!payload.messageId || !payload.from || !payload.body) { sendJson(res, 400, { error: "Missing fields" }); return true; }
  
  const sender = payload.from.replace(/^tell\\//, "");
  const content = payload.subject ? `**${payload.subject}**\\n\\n${payload.body}` : payload.body;
  
  try {
    await state.runtime.routeInboundMessage({
      channel: "clawtell", accountId: state.config?.name ?? "default",
      senderId: `tell/${sender}`, senderDisplay: sender,
      chatId: payload.threadId ?? `dm:${sender}`, chatType: payload.threadId ? "thread" : "direct",
      messageId: payload.messageId, text: content, timestamp: new Date(payload.timestamp || Date.now()),
      replyToId: payload.replyToMessageId, metadata: { clawtell: { autoReplyEligible: payload.autoReplyEligible, subject: payload.subject } },
    });
    sendJson(res, 200, { received: true, messageId: payload.messageId });
  } catch (e) { console.error("[clawtell] Route failed:", e); sendJson(res, 500, { error: "Failed to process" }); }
  return true;
}

const clawtellChannel = {
  id: "clawtell",
  meta: { id: "clawtell", label: "ClawTell", selectionLabel: "ClawTell (Agent-to-Agent)", blurb: "Agent messaging via ClawTell", aliases: ["ct", "tell"], order: 80 },
  capabilities: { chatTypes: ["direct"], media: true, reactions: false, edit: false, unsend: false, reply: true },
  config: {
    listAccountIds: (cfg: any) => { const c = cfg.channels?.clawtell; if (!c) return []; const ids: string[] = []; if (c.name && c.apiKey) ids.push("default"); if (c.accounts) ids.push(...Object.keys(c.accounts)); return ids; },
    resolveAccount: (cfg: any, accountId?: string) => { const c = cfg.channels?.clawtell ?? {}; const d = !accountId || accountId === "default"; const a = d ? c : c.accounts?.[accountId]; return { accountId: accountId ?? "default", name: a?.name ?? accountId ?? "default", enabled: a?.enabled ?? (d && c.enabled) ?? false, configured: Boolean(a?.name && a?.apiKey), apiKey: a?.apiKey ?? null, tellName: a?.name ?? null, pollIntervalMs: a?.pollIntervalMs ?? 30000, webhookPath: a?.webhookPath ?? "/webhook/clawtell", webhookSecret: a?.webhookSecret ?? null, gatewayUrl: a?.gatewayUrl ?? null, config: a ?? {} }; },
    defaultAccountId: () => "default", isConfigured: (a: any) => a.configured,
    describeAccount: (a: any) => ({ accountId: a.accountId, name: a.name, enabled: a.enabled, configured: a.configured }),
  },
  messaging: { normalizeTarget: (t: string) => t?.trim().toLowerCase().replace(/^tell\\//, "") || null, formatTargetDisplay: ({ target }: any) => `tell/${target?.replace(/^tell\\//, "") ?? ""}` },
  outbound: {
    deliveryMode: "direct", textChunkLimit: 50000,
    resolveTarget: ({ to }: any) => to?.trim() ? { ok: true, to: to.trim().toLowerCase().replace(/^tell\\//, "") } : { ok: false, error: new Error("Missing --to") },
    sendText: async ({ cfg, to, text, accountId, replyToId }: any) => { const a = clawtellChannel.config.resolveAccount(cfg, accountId); if (!a.apiKey) return { ok: false, error: new Error("No API key") }; return { channel: "clawtell", ...await sendMessage({ apiKey: a.apiKey, to, body: text, replyToId }) }; },
    sendMedia: async ({ cfg, to, caption, mediaUrl, accountId, replyToId }: any) => { const a = clawtellChannel.config.resolveAccount(cfg, accountId); if (!a.apiKey) return { ok: false, error: new Error("No API key") }; const body = mediaUrl ? `${caption ?? "Attachment"}\\n\\n📎 ${mediaUrl}` : caption ?? ""; return { channel: "clawtell", ...await sendMessage({ apiKey: a.apiKey, to, body, replyToId }) }; },
  },
  status: { probeAccount: async ({ account }: any) => account.apiKey ? probeApi(account.apiKey) : { ok: false, error: "No API key" } },
  gateway: {
    startAccount: async (ctx: any) => {
      const { account, cfg, abortSignal, setStatus, log } = ctx;
      setStatus({ accountId: account.accountId, running: true, lastStartAt: new Date().toISOString() });
      log?.info(`[clawtell] Starting (name=${account.tellName})`);
      
      const gatewayUrl = account.gatewayUrl || cfg.gateway?.publicUrl || cfg.gateway?.url;
      if (gatewayUrl && account.apiKey && account.tellName) {
        let secret = account.webhookSecret;
        if (!secret) { secret = randomBytes(32).toString("hex"); state.generatedSecrets.set(account.accountId, secret); log?.info("[clawtell] Generated webhook secret"); }
        const webhookUrl = gatewayUrl.replace(/\\/$/, "") + account.webhookPath;
        const reg = await registerGateway({ apiKey: account.apiKey, tellName: account.tellName, webhookUrl, webhookSecret: secret });
        reg.ok ? log?.info(`[clawtell] Registered gateway: ${webhookUrl}`) : log?.warn(`[clawtell] Gateway registration failed: ${reg.error}`);
      }
      
      const processedIds = new Set<string>();
      while (!abortSignal.aborted) {
        try {
          for (const msg of await fetchInbox(account.apiKey)) {
            if (processedIds.has(msg.id)) continue;
            processedIds.add(msg.id);
            if (processedIds.size > 1000) { const a = Array.from(processedIds); processedIds.clear(); a.slice(-500).forEach(i => processedIds.add(i)); }
            const sender = msg.from.replace(/^tell\\//, "");
            const content = msg.subject ? `**${msg.subject}**\\n\\n${msg.body}` : msg.body;
            await state.runtime.routeInboundMessage({ channel: "clawtell", accountId: account.accountId, senderId: `tell/${sender}`, senderDisplay: sender, chatId: msg.thread_id ?? `dm:${sender}`, chatType: msg.thread_id ? "thread" : "direct", messageId: msg.id, text: content, timestamp: new Date(msg.created_at), replyToId: msg.reply_to_id, metadata: { clawtell: { autoReplyEligible: msg.auto_reply_eligible } } });
            await markAsRead(account.apiKey, msg.id);
            setStatus({ lastInboundAt: new Date().toISOString() });
          }
        } catch (e: any) { setStatus({ lastError: e.message }); }
        await new Promise<void>(r => { const t = setTimeout(r, account.pollIntervalMs); abortSignal.addEventListener("abort", () => { clearTimeout(t); r(); }, { once: true }); });
      }
      setStatus({ running: false, lastStopAt: new Date().toISOString() });
    },
  },
};

const plugin = {
  id: "clawtell", name: "ClawTell", description: "ClawTell channel - agent-to-agent messaging", configSchema: emptyPluginConfigSchema(),
  register(api: ClawdbotPluginApi) {
    state.runtime = api.runtime;
    const cfg = api.runtime.getConfig?.();
    if (cfg?.channels?.clawtell) state.config = cfg.channels.clawtell;
    api.registerChannel({ plugin: clawtellChannel as any });
    api.registerHttpHandler(handleWebhook);
    console.log("🐾 ClawTell plugin loaded");
  },
};
export default plugin;
'''
    
    # Write files
    with open(os.path.join(plugin_dir, "clawdbot.plugin.json"), "w") as f:
        json.dump(plugin_json, f, indent=2)
    
    with open(os.path.join(plugin_dir, "index.ts"), "w") as f:
        f.write(index_ts)
    
    print(f"✅ Plugin installed to ~/.clawdbot/extensions/clawtell/")
    print()
    print("📝 Add this to your Clawdbot config (~/.clawdbot/clawdbot.json):")
    print()
    print('   "channels": {')
    print('     "clawtell": {')
    print('       "enabled": true,')
    print('       "name": "YOUR_NAME",')
    print('       "apiKey": "claw_xxx_yyy"')
    print('     }')
    print('   }')
    print()
    print("Then restart Clawdbot: clawdbot gateway restart")


def main():
    """Main CLI entry point."""
    parser = argparse.ArgumentParser(
        description="ClawTell CLI - Universal messaging for AI agents",
        prog="clawtell",
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")
    
    # init command
    init_parser = subparsers.add_parser(
        "init",
        help="Initialize a webhook handler project",
    )
    init_parser.add_argument(
        "directory",
        nargs="?",
        default=".",
        help="Target directory (default: current directory)",
    )
    init_parser.add_argument(
        "--force", "-f",
        action="store_true",
        help="Overwrite existing files",
    )
    init_parser.set_defaults(func=cmd_init)
    
    # version command
    version_parser = subparsers.add_parser(
        "version",
        help="Print version information",
    )
    version_parser.set_defaults(func=cmd_version)
    
    # setup-clawdbot command
    clawdbot_parser = subparsers.add_parser(
        "setup-clawdbot",
        help="Install ClawTell channel plugin for Clawdbot",
    )
    clawdbot_parser.set_defaults(func=cmd_setup_clawdbot)
    
    # Parse and execute
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(0)
    
    args.func(args)


if __name__ == "__main__":
    main()
