[object Object]

← back to Designer Wallcoverings

security: purge leaked ANTHROPIC_API_KEY from tracked marketing-agent.ts.backup

62f26ac46bb6af392792f6c6886146cbf6c33d76 · 2026-09-17 16:48:36 -0700 · Steve

Code-review finding (2026-09-17): live sk-ant key was still present in this
tracked backup file even after ecosystem.config.cjs was env-ified. *.backup
was already gitignored (the file predated the rule). Removes the working-tree
leak. NOTE: key remains in git history — rotation in the Anthropic console is
the actual revocation and is still required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1fbVDeQRfYhz5QnxYoPU

Files touched

Diff

commit 62f26ac46bb6af392792f6c6886146cbf6c33d76
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Sep 17 16:48:36 2026 -0700

    security: purge leaked ANTHROPIC_API_KEY from tracked marketing-agent.ts.backup
    
    Code-review finding (2026-09-17): live sk-ant key was still present in this
    tracked backup file even after ecosystem.config.cjs was env-ified. *.backup
    was already gitignored (the file predated the rule). Removes the working-tree
    leak. NOTE: key remains in git history — rotation in the Anthropic console is
    the actual revocation and is still required.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01JJ1fbVDeQRfYhz5QnxYoPU
---
 DW-Agents/dw-agents/marketing-agent.ts.backup | 1993 -------------------------
 1 file changed, 1993 deletions(-)

diff --git a/DW-Agents/dw-agents/marketing-agent.ts.backup b/DW-Agents/dw-agents/marketing-agent.ts.backup
deleted file mode 100644
index 8407d3e2..00000000
--- a/DW-Agents/dw-agents/marketing-agent.ts.backup
+++ /dev/null
@@ -1,1993 +0,0 @@
-/**
- * DW-Agents: Marketing Agent
- *
- * AI-powered marketing content creation and management:
- * 1. View and manage Shopify blog posts
- * 2. Create compelling blog posts from new SKUs
- * 3. Generate social media content (Instagram, Facebook)
- * 4. Draft email campaigns (Constant Contact integration)
- * 5. Chat interface for content creation assistance
- *
- * Port: 9881
- */
-
-import Anthropic from '@anthropic-ai/sdk';
-import express from 'express';
-import { requireAuth as ssoAuth, handleLogin, setSSOToken, SSO_TOKEN_NAME, SSO_TOKEN_VALUE } from './shared-auth.js';
-import cookieParser from 'cookie-parser';
-import session from 'express-session';
-import fetch from 'node-fetch';
-import { AgentMemory } from './shared-memory-system.js';
-
-// Import theme utilities
-const { getThemeStyles } = require('./shared-ui-components.js');
-
-const app = express();
-const PORT = 9881;
-// Initialize agent memory system
-const agentMemory = new AgentMemory('marketing');
-console.log('📚 Memory system initialized for marketing');
-
-
-// Authentication
-const AUTH_USERNAME = 'admin';
-const AUTH_PASSWORD = '2025';
-
-// Shopify configuration
-const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
-const SHOPIFY_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN || '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';
-const API_VERSION = '2024-07';
-
-// Constant Contact configuration (placeholder)
-const CC_API_KEY = process.env.CONSTANT_CONTACT_API_KEY || '';
-const CC_ACCESS_TOKEN = process.env.CONSTANT_CONTACT_ACCESS_TOKEN || '';
-
-// Anthropic Claude
-const anthropic = new Anthropic({
-  apiKey: process.env.ANTHROPIC_API_KEY || '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'
-});
-
-// Types for Shopify orders
-interface OrderLineItem {
-  title: string;
-  sku: string;
-  quantity: number;
-  price?: string;
-}
-
-interface ShopifyOrder {
-  id: string;
-  name: string;
-  createdAt: string;
-  totalPriceSet: {
-    shopMoney: {
-      amount: string;
-      currencyCode: string;
-    }
-  };
-  displayFinancialStatus: string;
-  displayFulfillmentStatus: string;
-  lineItems: {
-    edges: Array<{
-      node: OrderLineItem;
-    }>;
-  };
-}
-
-// Stats tracking
-let stats = {
-  totalConversations: 0,
-  blogPostsCreated: 0,
-  socialPostsGenerated: 0,
-  emailCampaignsDrafted: 0,
-  lastBlogPost: null as { title: string; sku: string; timestamp: Date } | null,
-  recentBlogs: [] as Array<{
-    id: string;
-    title: string;
-    createdAt: string;
-    publishedAt: string | null;
-    tags: string;
-  }>,
-  recentProducts: [] as Array<{
-    id: string;
-    title: string;
-    sku: string;
-    vendor: string;
-    handle: string;
-    imageUrl: string;
-  }>,
-  // New order tracking stats
-  recentOrders: [] as ShopifyOrder[],
-  orderTrends: {
-    totalRevenue: 0,
-    orderCount: 0,
-    averageOrderValue: 0,
-    topProducts: [] as Array<{
-      title: string;
-      sku: string;
-      totalQuantity: number;
-      revenue: number;
-    }>,
-    dailyRevenue: [] as Array<{
-      date: string;
-      revenue: number;
-      orderCount: number;
-    }>
-  }
-};
-
-app.use(express.json());
-app.use(cookieParser());
-app.use(express.urlencoded({ extended: true }));
-
-app.use(
-  session({
-    secret: 'dw-marketing-agent-2025',
-    resave: false,
-    saveUninitialized: true,
-    rolling: true,
-    cookie: {
-      secure: false,
-      httpOnly: true,
-      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
-    },
-  })
-);
-
-declare module 'express-session' {
-  interface SessionData {
-    authenticated: boolean;
-    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
-  }
-}
-
-const requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => {
-  // TEMPORARY: Auth disabled for 4 hours
-  return next();
-
-  /* Original auth:
-
-  if (req.session.authenticated) {
-    next();
-  } else {
-    res.redirect('/login');
-  }
-  */
-};
-
-// Shopify API helper
-async function shopifyGraphQL(query: string, variables?: any) {
-  const response = await fetch(`https://${SHOPIFY_STORE}/admin/api/${API_VERSION}/graphql.json`, {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-      'X-Shopify-Access-Token': SHOPIFY_TOKEN,
-    },
-    body: JSON.stringify({ query, variables }),
-  });
-  return response.json();
-}
-
-// Fetch and analyze Shopify orders for marketing insights
-async function fetchAndAnalyzeOrders() {
-  const query = `
-    query {
-      orders(first: 50, reverse: true, sortKey: CREATED_AT) {
-        edges {
-          node {
-            id
-            name
-            createdAt
-            totalPriceSet {
-              shopMoney {
-                amount
-                currencyCode
-              }
-            }
-            displayFinancialStatus
-            displayFulfillmentStatus
-            lineItems(first: 20) {
-              edges {
-                node {
-                  title
-                  sku
-                  quantity
-                  originalUnitPriceSet {
-                    shopMoney {
-                      amount
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  `;
-
-  try {
-    const response = await shopifyGraphQL(query);
-
-    if (response.data?.orders?.edges) {
-      const orders = response.data.orders.edges.map((edge: any) => edge.node);
-      stats.recentOrders = orders;
-
-      // Analyze order trends
-      analyzeOrderTrends(orders);
-
-      return orders;
-    }
-    return [];
-  } catch (error) {
-    console.error('Error fetching orders:', error);
-    return [];
-  }
-}
-
-// Analyze order data for marketing insights
-function analyzeOrderTrends(orders: ShopifyOrder[]) {
-  // Reset trends
-  stats.orderTrends = {
-    totalRevenue: 0,
-    orderCount: orders.length,
-    averageOrderValue: 0,
-    topProducts: [],
-    dailyRevenue: []
-  };
-
-  // Product aggregation map
-  const productMap = new Map<string, {
-    title: string;
-    sku: string;
-    totalQuantity: number;
-    revenue: number;
-  }>();
-
-  // Daily revenue map
-  const dailyMap = new Map<string, {
-    revenue: number;
-    orderCount: number;
-  }>();
-
-  // Process each order
-  orders.forEach(order => {
-    const orderAmount = parseFloat(order.totalPriceSet.shopMoney.amount);
-    stats.orderTrends.totalRevenue += orderAmount;
-
-    // Extract date (YYYY-MM-DD)
-    const orderDate = order.createdAt.split('T')[0];
-    if (!dailyMap.has(orderDate)) {
-      dailyMap.set(orderDate, { revenue: 0, orderCount: 0 });
-    }
-    const dailyData = dailyMap.get(orderDate)!;
-    dailyData.revenue += orderAmount;
-    dailyData.orderCount += 1;
-
-    // Process line items
-    order.lineItems.edges.forEach(edge => {
-      const item = edge.node;
-      const key = item.sku || item.title;
-
-      if (!productMap.has(key)) {
-        productMap.set(key, {
-          title: item.title,
-          sku: item.sku || 'N/A',
-          totalQuantity: 0,
-          revenue: 0
-        });
-      }
-
-      const product = productMap.get(key)!;
-      product.totalQuantity += item.quantity;
-      if (item.price) {
-        product.revenue += parseFloat(item.price) * item.quantity;
-      }
-    });
-  });
-
-  // Calculate average order value
-  stats.orderTrends.averageOrderValue = orders.length > 0
-    ? stats.orderTrends.totalRevenue / orders.length
-    : 0;
-
-  // Convert product map to sorted array (top 10 by quantity)
-  stats.orderTrends.topProducts = Array.from(productMap.values())
-    .sort((a, b) => b.totalQuantity - a.totalQuantity)
-    .slice(0, 10);
-
-  // Convert daily map to sorted array (last 7 days)
-  stats.orderTrends.dailyRevenue = Array.from(dailyMap.entries())
-    .map(([date, data]) => ({
-      date,
-      revenue: data.revenue,
-      orderCount: data.orderCount
-    }))
-    .sort((a, b) => b.date.localeCompare(a.date))
-    .slice(0, 7);
-}
-
-// Load orders on startup
-fetchAndAnalyzeOrders().then(() => {
-  console.log(`📊 Loaded ${stats.recentOrders.length} orders for analysis`);
-});
-
-// Refresh orders every 30 minutes
-setInterval(() => {
-  fetchAndAnalyzeOrders();
-}, 30 * 60 * 1000);
-
-// Login page
-app.get('/login', (req, res) => {
-  res.send(`
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Login - Marketing Agent</title>
-      ${getThemeStyles('modern-minimalist')}
-  <style>
-    * { margin: 0; padding: 0; box-sizing: border-box; }
-    body {
-      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      min-height: 100vh;
-    }
-    .login-container {
-      background: white;
-      padding: 40px;
-      border-radius: 15px;
-      box-shadow: 0 15px 35px rgba(0,0,0,0.2);
-      max-width: 400px;
-      width: 100%;
-    }
-    h1 { color: #f5576c; text-align: center; margin-bottom: 30px; }
-    input {
-      width: 100%;
-      padding: 12px;
-      margin: 10px 0;
-      border: 2px solid #e0e0e0;
-      border-radius: 8px;
-    }
-    button {
-      width: 100%;
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      color: white;
-      border: none;
-      padding: 15px;
-      border-radius: 8px;
-      cursor: pointer;
-      font-weight: 600;
-      margin-top: 10px;
-    }
-    .error { color: red; text-align: center; margin-bottom: 15px; }
-  </style>
-</head>
-<body>
-  <div class="login-container">
-    <h1>📢 Marketing Agent</h1>
-    <p style="text-align: center; color: #666; margin-bottom: 20px;">Content Creation & Campaigns</p>
-    ${req.query.error ? '<div class="error">Invalid credentials</div>' : ''}
-    <form method="POST">
-      <input type="text" name="username" placeholder="Username" required>
-      <input type="password" name="password" placeholder="Password" required>
-      <button type="submit">Login</button>
-    </form>
-  </div>
-</body>
-</html>
-  `);
-});
-
-app.post('/login', (req, res) => {
-  const { username, password } = req.body;
-  if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
-    req.session.authenticated = true;
-    req.session.chatHistory = [];
-    res.redirect('/');
-  } else {
-    res.redirect('/login?error=1');
-  }
-});
-
-app.get('/logout', (req, res) => {
-  req.session.destroy(() => res.redirect('/login'));
-});
-
-// Main dashboard
-app.get('/', requireAuth, async (req, res) => {
-  res.send(`
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Marketing Agent - DW-Agents</title>
-      ${getThemeStyles('modern-minimalist')}
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <style>
-    * { margin: 0; padding: 0; box-sizing: border-box; }
-    body {
-      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      padding: 20px;
-      min-height: 100vh;
-    }
-    .container {
-      max-width: 1600px;
-      margin: 0 auto;
-      display: grid;
-      grid-template-columns: 400px 1fr;
-      gap: 20px;
-    }
-
-    /* Mobile-responsive sidebar */
-    .sidebar {
-      display: flex;
-      flex-direction: column;
-      gap: 20px;
-    }
-    .sidebar-toggle {
-      display: none;
-      position: fixed;
-      top: 20px;
-      left: 20px;
-      z-index: 1001;
-      background: white;
-      border: none;
-      border-radius: 50%;
-      width: 50px;
-      height: 50px;
-      box-shadow: 0 4px 12px rgba(0,0,0,0.2);
-      cursor: pointer;
-      font-size: 1.5em;
-      color: #f5576c;
-      transition: transform 0.3s;
-    }
-    .sidebar-toggle:hover {
-      transform: scale(1.1);
-    }
-    .sidebar-toggle:focus {
-      outline: 3px solid #f5576c;
-      outline-offset: 2px;
-    }
-
-    .header {
-      background: white;
-      padding: 30px;
-      border-radius: 15px;
-      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
-    }
-    .header h1 { color: #d8194e; font-size: 2em; margin-bottom: 10px; }
-    .header .subtitle { color: #333; font-size: 1.1em; }
-    .stats-card {
-      background: white;
-      padding: 20px;
-      border-radius: 15px;
-      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
-    }
-    .stats-card h3 { color: #555; font-size: 0.85em; text-transform: uppercase; margin-bottom: 8px; }
-    .stats-card .value { font-size: 2em; font-weight: bold; color: #d8194e; }
-    .main-content {
-      display: flex;
-      flex-direction: column;
-      gap: 20px;
-    }
-    .tab-container {
-      background: white;
-      border-radius: 15px;
-      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
-      overflow: hidden;
-    }
-
-    /* Scrollable tabs for mobile */
-    .tabs {
-      display: flex;
-      background: #f5f5f5;
-      border-bottom: 2px solid #e0e0e0;
-      overflow-x: auto;
-      -webkit-overflow-scrolling: touch;
-      scrollbar-width: thin;
-    }
-    .tabs::-webkit-scrollbar {
-      height: 4px;
-    }
-    .tabs::-webkit-scrollbar-track {
-      background: #f1f1f1;
-    }
-    .tabs::-webkit-scrollbar-thumb {
-      background: #f5576c;
-      border-radius: 4px;
-    }
-    .tab {
-      flex: 1;
-      min-width: 140px;
-      padding: 15px;
-      text-align: center;
-      cursor: pointer;
-      font-weight: 600;
-      color: #333;
-      transition: all 0.3s;
-      white-space: nowrap;
-    }
-    .tab:hover {
-      background: #f0f0f0;
-    }
-    .tab:focus {
-      outline: 3px solid #f5576c;
-      outline-offset: -3px;
-      z-index: 1;
-    }
-    .tab.active {
-      background: white;
-      color: #d8194e;
-      border-bottom: 3px solid #d8194e;
-    }
-    .tab-content {
-      padding: 30px;
-      display: none;
-    }
-    .tab-content.active {
-      display: block;
-    }
-
-    /* Loading spinner */
-    .spinner {
-      display: inline-block;
-      width: 20px;
-      height: 20px;
-      border: 3px solid rgba(255,255,255,0.3);
-      border-radius: 50%;
-      border-top-color: white;
-      animation: spin 0.8s linear infinite;
-    }
-    @keyframes spin {
-      to { transform: rotate(360deg); }
-    }
-    .loading-overlay {
-      position: fixed;
-      top: 0;
-      left: 0;
-      right: 0;
-      bottom: 0;
-      background: rgba(0,0,0,0.5);
-      display: none;
-      justify-content: center;
-      align-items: center;
-      z-index: 9999;
-    }
-    .loading-overlay.active {
-      display: flex;
-    }
-    .loading-content {
-      background: white;
-      padding: 30px 40px;
-      border-radius: 15px;
-      text-align: center;
-      box-shadow: 0 10px 40px rgba(0,0,0,0.3);
-    }
-    .loading-spinner {
-      width: 50px;
-      height: 50px;
-      border: 4px solid #f0f0f0;
-      border-top-color: #f5576c;
-      border-radius: 50%;
-      animation: spin 0.8s linear infinite;
-      margin: 0 auto 15px;
-    }
-
-    /* Toast notifications */
-    .toast {
-      position: fixed;
-      top: 20px;
-      right: 20px;
-      background: white;
-      padding: 16px 24px;
-      border-radius: 10px;
-      box-shadow: 0 8px 24px rgba(0,0,0,0.2);
-      display: flex;
-      align-items: center;
-      gap: 12px;
-      z-index: 10000;
-      transform: translateX(400px);
-      transition: transform 0.3s ease-out;
-      max-width: 350px;
-    }
-    .toast.show {
-      transform: translateX(0);
-    }
-    .toast.error {
-      border-left: 4px solid #dc3545;
-    }
-    .toast.success {
-      border-left: 4px solid #28a745;
-    }
-    .toast.info {
-      border-left: 4px solid #17a2b8;
-    }
-    .toast-icon {
-      font-size: 1.5em;
-      flex-shrink: 0;
-    }
-    .toast-message {
-      flex: 1;
-      color: #333;
-      line-height: 1.4;
-    }
-    .toast-close {
-      background: none;
-      border: none;
-      color: #888;
-      cursor: pointer;
-      font-size: 1.2em;
-      padding: 0;
-      width: 24px;
-      height: 24px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      border-radius: 4px;
-      flex-shrink: 0;
-    }
-    .toast-close:hover {
-      background: #f0f0f0;
-      color: #333;
-    }
-    .toast-close:focus {
-      outline: 2px solid #f5576c;
-    }
-
-    .chat-container {
-      height: 500px;
-      display: flex;
-      flex-direction: column;
-    }
-    .chat-messages {
-      flex: 1;
-      overflow-y: auto;
-      padding: 20px;
-      background: #f5f5f5;
-      border-radius: 10px;
-      margin-bottom: 20px;
-    }
-    .message {
-      margin-bottom: 15px;
-      display: flex;
-      gap: 10px;
-    }
-    .message.user {
-      flex-direction: row-reverse;
-    }
-    .message-avatar {
-      width: 40px;
-      height: 40px;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 1.2em;
-      flex-shrink: 0;
-    }
-    .message.user .message-avatar {
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-    }
-    .message.assistant .message-avatar {
-      background: #e0e0e0;
-    }
-    .message-content {
-      max-width: 70%;
-      padding: 12px 16px;
-      border-radius: 12px;
-      line-height: 1.5;
-    }
-    .message.user .message-content {
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      color: white;
-      border-bottom-right-radius: 4px;
-    }
-    .message.assistant .message-content {
-      background: white;
-      color: #1a1a1a;
-      border-bottom-left-radius: 4px;
-      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
-    }
-    .chat-input-container {
-      display: flex;
-      gap: 10px;
-    }
-    #chatInput {
-      flex: 1;
-      padding: 12px 16px;
-      border: 2px solid #e0e0e0;
-      border-radius: 25px;
-      font-size: 1em;
-      outline: none;
-      color: #1a1a1a;
-    }
-    #chatInput:focus {
-      border-color: #f5576c;
-      box-shadow: 0 0 0 3px rgba(245, 87, 108, 0.1);
-    }
-    .send-btn {
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      color: white;
-      border: none;
-      padding: 12px 30px;
-      border-radius: 25px;
-      cursor: pointer;
-      font-weight: 600;
-      transition: all 0.2s;
-    }
-    .send-btn:hover:not(:disabled) {
-      transform: translateY(-1px);
-      box-shadow: 0 4px 12px rgba(245, 87, 108, 0.3);
-    }
-    .send-btn:focus {
-      outline: 3px solid #f5576c;
-      outline-offset: 2px;
-    }
-    .send-btn:active:not(:disabled) {
-      transform: translateY(0);
-    }
-    .send-btn:disabled {
-      opacity: 0.6;
-      cursor: not-allowed;
-    }
-    .product-grid {
-      display: grid;
-      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-      gap: 15px;
-      max-height: 400px;
-      overflow-y: auto;
-    }
-    .product-card {
-      border: 2px solid #e0e0e0;
-      border-radius: 10px;
-      padding: 15px;
-      cursor: pointer;
-      transition: all 0.3s;
-    }
-    .product-card:hover {
-      border-color: #f5576c;
-      transform: translateY(-2px);
-      box-shadow: 0 5px 15px rgba(0,0,0,0.1);
-    }
-    .product-card:focus {
-      outline: 3px solid #f5576c;
-      outline-offset: 2px;
-    }
-    .product-card.selected {
-      border-color: #f5576c;
-      background: #fff5f7;
-    }
-    .product-image {
-      width: 100%;
-      height: 150px;
-      object-fit: cover;
-      border-radius: 8px;
-      margin-bottom: 10px;
-    }
-    .product-title {
-      font-weight: 600;
-      font-size: 0.9em;
-      margin-bottom: 5px;
-      color: #1a1a1a;
-    }
-    .product-sku {
-      font-size: 0.8em;
-      color: #555;
-    }
-    .blog-list {
-      max-height: 400px;
-      overflow-y: auto;
-    }
-    .blog-item {
-      padding: 15px;
-      border-bottom: 1px solid #e0e0e0;
-      cursor: pointer;
-      transition: background 0.2s;
-    }
-    .blog-item:hover {
-      background: #f5f5f5;
-    }
-    .blog-item:focus {
-      outline: 2px solid #f5576c;
-      outline-offset: -2px;
-      background: #f5f5f5;
-    }
-    .blog-title {
-      font-weight: 600;
-      color: #1a1a1a;
-      margin-bottom: 5px;
-    }
-    .blog-meta {
-      font-size: 0.85em;
-      color: #555;
-    }
-    .btn {
-      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
-      color: white;
-      border: none;
-      padding: 12px 24px;
-      border-radius: 8px;
-      cursor: pointer;
-      font-weight: 600;
-      margin-top: 15px;
-      transition: all 0.2s;
-    }
-    .btn:hover:not(:disabled) {
-      transform: translateY(-1px);
-      box-shadow: 0 4px 12px rgba(245, 87, 108, 0.3);
-    }
-    .btn:focus {
-      outline: 3px solid #d8194e;
-      outline-offset: 2px;
-    }
-    .btn:active:not(:disabled) {
-      transform: translateY(0);
-    }
-    .btn:disabled {
-      opacity: 0.5;
-      cursor: not-allowed;
-    }
-    .suggestions {
-      display: flex;
-      gap: 10px;
-      flex-wrap: wrap;
-      margin-bottom: 15px;
-    }
-    .suggestion-chip {
-      background: #f0f0f0;
-      padding: 8px 15px;
-      border-radius: 20px;
-      font-size: 0.85em;
-      cursor: pointer;
-      transition: all 0.2s;
-      color: #333;
-    }
-    .suggestion-chip:hover {
-      background: #f5576c;
-      color: white;
-    }
-    .suggestion-chip:focus {
-      outline: 2px solid #f5576c;
-      outline-offset: 2px;
-    }
-
-    /* Mobile responsive */
-    @media (max-width: 1024px) {
-      .container {
-        grid-template-columns: 1fr;
-      }
-      .sidebar {
-        position: fixed;
-        left: 0;
-        top: 0;
-        bottom: 0;
-        width: 300px;
-        background: white;
-        z-index: 1000;
-        padding: 20px;
-        overflow-y: auto;
-        transform: translateX(-100%);
-        transition: transform 0.3s ease-out;
-        box-shadow: 2px 0 8px rgba(0,0,0,0.1);
-      }
-      .sidebar.open {
-        transform: translateX(0);
-      }
-      .sidebar-toggle {
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-      .sidebar-overlay {
-        display: none;
-        position: fixed;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        background: rgba(0,0,0,0.5);
-        z-index: 999;
-      }
-      .sidebar-overlay.active {
-        display: block;
-      }
-      .main-content {
-        margin-top: 70px;
-      }
-      .tab {
-        font-size: 0.9em;
-        padding: 12px 10px;
-      }
-      .message-content {
-        max-width: 85%;
-      }
-    }
-
-    @media (max-width: 640px) {
-      body {
-        padding: 10px;
-      }
-      .tab-content {
-        padding: 20px 15px;
-      }
-      .chat-container {
-        height: 400px;
-      }
-      .product-grid {
-        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
-        gap: 10px;
-      }
-      .toast {
-        right: 10px;
-        left: 10px;
-        max-width: none;
-      }
-    }
-  </style>
-</head>
-<body>
-  <!-- Mobile sidebar toggle -->
-  <button class="sidebar-toggle" onclick="toggleSidebar()" aria-label="Toggle menu">☰</button>
-  <div class="sidebar-overlay" onclick="toggleSidebar()"></div>
-
-  <!-- Loading overlay -->
-  <div class="loading-overlay" id="loadingOverlay">
-    <div class="loading-content">
-      <div class="loading-spinner"></div>
-      <div style="color: #333; font-weight: 600;">Processing...</div>
-    </div>
-  </div>
-
-  <div class="container">
-    <div class="sidebar" id="sidebar">
-      <div class="header">
-        <h1>📢 Marketing Agent</h1>
-        <div class="subtitle">Content Creation & Campaigns</div>
-        <div style="margin-top: 15px;">
-          <a href="/logout" style="color: #f5576c; text-decoration: none;">Logout</a>
-        </div>
-      </div>
-
-      <div class="stats-card">
-        <h3>Blog Posts Created</h3>
-        <div class="value" id="blogPostsCreated">0</div>
-      </div>
-
-      <div class="stats-card">
-        <h3>Social Posts</h3>
-        <div class="value" id="socialPostsGenerated">0</div>
-      </div>
-
-      <div class="stats-card">
-        <h3>Email Campaigns</h3>
-        <div class="value" id="emailCampaignsDrafted">0</div>
-      </div>
-
-      <div class="stats-card">
-        <h3>Conversations</h3>
-        <div class="value" id="totalConversations">0</div>
-      </div>
-    </div>
-
-    <div class="main-content">
-      <div class="tab-container">
-        <div class="tabs">
-          <div class="tab active" onclick="switchTab('insights')" tabindex="0" role="tab" aria-selected="true" onkeypress="if(event.key==='Enter') switchTab('insights')">📊 Order Insights</div>
-          <div class="tab" onclick="switchTab('chat')" tabindex="0" role="tab" aria-selected="false" onkeypress="if(event.key==='Enter') switchTab('chat')">💬 Chat Assistant</div>
-          <div class="tab" onclick="switchTab('blogs')" tabindex="0" role="tab" aria-selected="false" onkeypress="if(event.key==='Enter') switchTab('blogs')">📝 Blog Posts</div>
-          <div class="tab" onclick="switchTab('create')" tabindex="0" role="tab" aria-selected="false" onkeypress="if(event.key==='Enter') switchTab('create')">✨ Create from SKU</div>
-          <div class="tab" onclick="switchTab('social')" tabindex="0" role="tab" aria-selected="false" onkeypress="if(event.key==='Enter') switchTab('social')">📱 Social Media</div>
-        </div>
-
-        <!-- Order Insights Tab -->
-        <div class="tab-content active" id="insights-content">
-          <h2 style="color: #f5576c; margin-bottom: 15px;">Order Insights & Marketing Metrics</h2>
-
-          <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 30px;">
-            <div style="background: #f5f5f5; padding: 20px; border-radius: 10px; border-left: 4px solid #f5576c;">
-              <h3 style="color: #666; font-size: 0.9em; margin-bottom: 8px;">Total Revenue (30 days)</h3>
-              <div style="font-size: 1.8em; font-weight: bold; color: #333;">$<span id="totalRevenue">0</span></div>
-            </div>
-            <div style="background: #f5f5f5; padding: 20px; border-radius: 10px; border-left: 4px solid #36454f;">
-              <h3 style="color: #666; font-size: 0.9em; margin-bottom: 8px;">Total Orders</h3>
-              <div style="font-size: 1.8em; font-weight: bold; color: #333;"><span id="orderCount">0</span></div>
-            </div>
-            <div style="background: #f5f5f5; padding: 20px; border-radius: 10px; border-left: 4px solid #708090;">
-              <h3 style="color: #666; font-size: 0.9em; margin-bottom: 8px;">Average Order Value</h3>
-              <div style="font-size: 1.8em; font-weight: bold; color: #333;">$<span id="avgOrderValue">0</span></div>
-            </div>
-          </div>
-
-          <h3 style="color: #f5576c; margin-bottom: 15px;">🔥 Top Selling Products</h3>
-          <div id="topProducts" style="background: #f5f5f5; border-radius: 10px; padding: 20px; margin-bottom: 30px;">
-            <p style="color: #888;">Loading top products...</p>
-          </div>
-
-          <h3 style="color: #f5576c; margin-bottom: 15px;">📈 Daily Revenue Trend</h3>
-          <div id="dailyRevenue" style="background: #f5f5f5; border-radius: 10px; padding: 20px; margin-bottom: 30px;">
-            <p style="color: #888;">Loading revenue trends...</p>
-          </div>
-
-          <h3 style="color: #f5576c; margin-bottom: 15px;">🛍️ Recent Orders</h3>
-          <button class="btn" onclick="loadOrderInsights()">🔄 Refresh Order Data</button>
-          <div id="recentOrders" style="margin-top: 20px; max-height: 400px; overflow-y: auto;">
-            <p style="color: #888; padding: 20px;">Click "Refresh Order Data" to load recent orders...</p>
-          </div>
-        </div>
-
-        <!-- Chat Tab -->
-        <div class="tab-content" id="chat-content">
-          <h2 style="color: #f5576c; margin-bottom: 15px;">Chat with Marketing Agent</h2>
-          <div class="suggestions">
-            <div class="suggestion-chip" onclick="sendSuggestion('Create a professional blog post for product grasscloth in Architectural Digest style with images')" tabindex="0" onkeypress="if(event.key==='Enter') sendSuggestion('Create a professional blog post for product grasscloth in Architectural Digest style with images')">📰 AD-Style Blog</div>
-            <div class="suggestion-chip" onclick="sendSuggestion('Make blog for vendor York with photos and details')" tabindex="0" onkeypress="if(event.key==='Enter') sendSuggestion('Make blog for vendor York with photos and details')">✨ Vendor Blog</div>
-            <div class="suggestion-chip" onclick="sendSuggestion('Generate Instagram post')" tabindex="0" onkeypress="if(event.key==='Enter') sendSuggestion('Generate Instagram post')">📸 Instagram</div>
-            <div class="suggestion-chip" onclick="sendSuggestion('Show me recent blog posts')" tabindex="0" onkeypress="if(event.key==='Enter') sendSuggestion('Show me recent blog posts')">📝 Recent Blogs</div>
-          </div>
-          <div class="chat-container">
-            <div class="chat-messages" id="chatMessages">
-              <div class="message assistant">
-                <div class="message-avatar">🤖</div>
-                <div class="message-content">
-                  Hi! I'm your Marketing Agent, specialized in Architectural Digest-style editorial content.<br><br>
-                  <strong>I can create:</strong><br>
-                  • Professional blog posts with product images and links<br>
-                  • Editorial-style content in AD magazine format<br>
-                  • Social media posts (Instagram, Facebook, Pinterest)<br>
-                  • Product-focused articles with full details<br>
-                  <br>
-                  <strong>Try asking:</strong><br>
-                  "Make blog for product [name] by vendor [vendor] in professional style"<br>
-                  "Create AD-style blog for SKU [sku] with photos and details"<br>
-                  <br>
-                  What would you like to create today?
-                </div>
-              </div>
-            </div>
-            <div class="chat-input-container">
-              <input
-                type="text"
-                id="chatInput"
-                placeholder="Ask me to create content, view blogs, or generate posts..."
-                onkeypress="if(event.key === 'Enter') sendMessage()"
-              >
-              <button class="send-btn" onclick="sendMessage()" id="sendBtn">Send</button>
-            </div>
-          </div>
-        </div>
-
-        <!-- Blog Posts Tab -->
-        <div class="tab-content" id="blogs-content">
-          <h2 style="color: #f5576c; margin-bottom: 15px;">Recent Blog Posts from Shopify</h2>
-          <button class="btn" onclick="loadBlogs()">🔄 Refresh Blog List</button>
-          <div class="blog-list" id="blogList">
-            <p style="color: #888; padding: 20px;">Click "Refresh Blog List" to load recent posts from Shopify...</p>
-          </div>
-        </div>
-
-        <!-- Create from SKU Tab -->
-        <div class="tab-content" id="create-content">
-          <h2 style="color: #f5576c; margin-bottom: 15px;">Create Blog Post from Product</h2>
-          <button class="btn" onclick="loadProducts()">🔄 Load Recent Products</button>
-          <p style="color: #666; margin: 15px 0;">Select a product below to generate a blog post:</p>
-          <div class="product-grid" id="productGrid">
-            <p style="color: #888; padding: 20px; grid-column: 1/-1;">Click "Load Recent Products" to see available SKUs...</p>
-          </div>
-          <button class="btn" id="generateBlogBtn" style="display:none;" onclick="generateBlog()">✨ Generate Blog Post</button>
-        </div>
-
-        <!-- Social Media Tab -->
-        <div class="tab-content" id="social-content">
-          <h2 style="color: #f5576c; margin-bottom: 15px;">Social Media Content Generator</h2>
-          <p style="color: #666; margin-bottom: 20px;">Generate social media posts for Instagram, Facebook, and more!</p>
-          <button class="btn" onclick="generateSocial('instagram')">📸 Generate Instagram Post</button>
-          <button class="btn" onclick="generateSocial('facebook')">👍 Generate Facebook Post</button>
-          <button class="btn" onclick="generateSocial('pinterest')">📌 Generate Pinterest Description</button>
-          <div id="socialOutput" style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 10px; display: none;">
-            <h3 style="color: #f5576c; margin-bottom: 10px;">Generated Content:</h3>
-            <pre id="socialContent" style="white-space: pre-wrap; font-family: inherit; color: #333;"></pre>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <script>
-    let selectedProduct = null;
-
-    // Toast notification system
-    function showToast(message, type = 'info') {
-      const existingToast = document.querySelector('.toast');
-      if (existingToast) {
-        existingToast.remove();
-      }
-
-      const toast = document.createElement('div');
-      toast.className = \`toast \${type}\`;
-
-      const icons = {
-        success: '✓',
-        error: '✕',
-        info: 'ℹ'
-      };
-
-      toast.innerHTML = \`
-        <div class="toast-icon">\${icons[type] || icons.info}</div>
-        <div class="toast-message">\${message}</div>
-        <button class="toast-close" onclick="this.parentElement.remove()" aria-label="Close">×</button>
-      \`;
-
-      document.body.appendChild(toast);
-
-      setTimeout(() => toast.classList.add('show'), 10);
-
-      setTimeout(() => {
-        toast.classList.remove('show');
-        setTimeout(() => toast.remove(), 300);
-      }, 5000);
-    }
-
-    // Loading overlay functions
-    function showLoading() {
-      document.getElementById('loadingOverlay').classList.add('active');
-    }
-
-    function hideLoading() {
-      document.getElementById('loadingOverlay').classList.remove('active');
-    }
-
-    // Mobile sidebar toggle
-    function toggleSidebar() {
-      const sidebar = document.getElementById('sidebar');
-      const overlay = document.querySelector('.sidebar-overlay');
-      sidebar.classList.toggle('open');
-      overlay.classList.toggle('active');
-    }
-
-    function switchTab(tab) {
-      // Update tab states
-      document.querySelectorAll('.tab').forEach(t => {
-        t.classList.remove('active');
-        t.setAttribute('aria-selected', 'false');
-      });
-      document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
-
-      // Activate selected tab
-      event.target.classList.add('active');
-      event.target.setAttribute('aria-selected', 'true');
-      document.getElementById(tab + '-content').classList.add('active');
-
-      // Close sidebar on mobile after tab switch
-      if (window.innerWidth <= 1024) {
-        const sidebar = document.getElementById('sidebar');
-        const overlay = document.querySelector('.sidebar-overlay');
-        if (sidebar.classList.contains('open')) {
-          sidebar.classList.remove('open');
-          overlay.classList.remove('active');
-        }
-      }
-    }
-
-    function updateStats() {
-      fetch('/api/stats')
-        .then(r => r.json())
-        .then(data => {
-          document.getElementById('blogPostsCreated').textContent = data.blogPostsCreated;
-          document.getElementById('socialPostsGenerated').textContent = data.socialPostsGenerated;
-          document.getElementById('emailCampaignsDrafted').textContent = data.emailCampaignsDrafted;
-          document.getElementById('totalConversations').textContent = data.totalConversations;
-        });
-    }
-
-    function addMessage(role, content) {
-      const messagesDiv = document.getElementById('chatMessages');
-      const messageDiv = document.createElement('div');
-      messageDiv.className = \`message \${role}\`;
-
-      const avatar = document.createElement('div');
-      avatar.className = 'message-avatar';
-      avatar.textContent = role === 'user' ? '👤' : '🤖';
-
-      const contentDiv = document.createElement('div');
-      contentDiv.className = 'message-content';
-      contentDiv.innerHTML = content.replace(/\\n/g, '<br>');
-
-      messageDiv.appendChild(avatar);
-      messageDiv.appendChild(contentDiv);
-      messagesDiv.appendChild(messageDiv);
-
-      messagesDiv.scrollTop = messagesDiv.scrollHeight;
-    }
-
-    async function sendMessage() {
-      const input = document.getElementById('chatInput');
-      const sendBtn = document.getElementById('sendBtn');
-      const message = input.value.trim();
-
-      if (!message) return;
-
-      addMessage('user', message);
-      input.value = '';
-      sendBtn.disabled = true;
-      sendBtn.innerHTML = '<span class="spinner"></span>';
-      showLoading();
-
-      try {
-        const response = await fetch('/api/chat', {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify({ message })
-        });
-
-        if (!response.ok) {
-          throw new Error('Network response was not ok');
-        }
-
-        const data = await response.json();
-        addMessage('assistant', data.response);
-        updateStats();
-
-      } catch (error) {
-        console.error('Chat error:', error);
-        addMessage('assistant', 'Sorry, I encountered an error. Please try again.');
-        showToast('Failed to send message. Please try again.', 'error');
-      } finally {
-        hideLoading();
-        sendBtn.disabled = false;
-        sendBtn.textContent = 'Send';
-      }
-    }
-
-    function sendSuggestion(text) {
-      document.getElementById('chatInput').value = text;
-      sendMessage();
-    }
-
-    async function loadBlogs() {
-      const blogList = document.getElementById('blogList');
-      blogList.innerHTML = '<p style="padding: 20px; color: #888;">Loading blog posts...</p>';
-      showLoading();
-
-      try {
-        const response = await fetch('/api/shopify/blogs');
-
-        if (!response.ok) {
-          throw new Error('Failed to fetch blogs');
-        }
-
-        const data = await response.json();
-
-        if (data.blogs && data.blogs.length > 0) {
-          const shopifyDomain = 'designer-laboratory-sandbox.myshopify.com';
-          blogList.innerHTML = data.blogs.map(blog => {
-            const blogUrl = \`https://\${shopifyDomain}/blogs/news/\${blog.handle}\`;
-            return \`
-            <div class="blog-item" tabindex="0">
-              <div class="blog-title">
-                <a href="\${blogUrl}" target="_blank" style="color: #f5576c; text-decoration: none;">
-                  \${blog.title}
-                </a>
-              </div>
-              <div class="blog-meta">
-                Created: \${new Date(blog.createdAt).toLocaleDateString()} |
-                \${blog.publishedAt ? 'Published' : 'Draft'}
-                \${blog.tags ? ' | Tags: ' + blog.tags : ''}
-                <br>
-                <a href="\${blogUrl}" target="_blank" style="color: #666; font-size: 12px; text-decoration: none;">
-                  🔗 View on Shopify →
-                </a>
-              </div>
-            </div>
-          \`;
-          }).join('');
-          showToast(\`Loaded \${data.blogs.length} blog posts\`, 'success');
-        } else {
-          blogList.innerHTML = '<p style="padding: 20px; color: #888;">No blog posts found.</p>';
-          showToast('No blog posts found', 'info');
-        }
-      } catch (error) {
-        console.error('Error loading blogs:', error);
-        blogList.innerHTML = '<p style="padding: 20px; color: #dc3545;">Error loading blogs. Please try again.</p>';
-        showToast('Failed to load blog posts', 'error');
-      } finally {
-        hideLoading();
-      }
-    }
-
-    async function loadProducts() {
-      const productGrid = document.getElementById('productGrid');
-      productGrid.innerHTML = '<p style="padding: 20px; color: #888; grid-column: 1/-1;">Loading products...</p>';
-      showLoading();
-
-      try {
-        const response = await fetch('/api/shopify/products');
-
-        if (!response.ok) {
-          throw new Error('Failed to fetch products');
-        }
-
-        const data = await response.json();
-
-        if (data.products && data.products.length > 0) {
-          productGrid.innerHTML = data.products.map(product => \`
-            <div class="product-card" tabindex="0" onclick="selectProduct('\${product.id}', '\${product.sku}', '\${product.title}', '\${product.imageUrl}')" onkeypress="if(event.key==='Enter') selectProduct('\${product.id}', '\${product.sku}', '\${product.title}', '\${product.imageUrl}')">
-              <img src="\${product.imageUrl || 'https://via.placeholder.com/200'}" class="product-image" alt="\${product.title}">
-              <div class="product-title">\${product.title}</div>
-              <div class="product-sku">SKU: \${product.sku}</div>
-            </div>
-          \`).join('');
-          showToast(\`Loaded \${data.products.length} products\`, 'success');
-        } else {
-          productGrid.innerHTML = '<p style="padding: 20px; color: #888; grid-column: 1/-1;">No recent products found.</p>';
-          showToast('No products found', 'info');
-        }
-      } catch (error) {
-        console.error('Error loading products:', error);
-        productGrid.innerHTML = '<p style="padding: 20px; color: #dc3545; grid-column: 1/-1;">Error loading products. Please try again.</p>';
-        showToast('Failed to load products', 'error');
-      } finally {
-        hideLoading();
-      }
-    }
-
-    function selectProduct(id, sku, title, imageUrl) {
-      document.querySelectorAll('.product-card').forEach(card => card.classList.remove('selected'));
-      event.currentTarget.classList.add('selected');
-      selectedProduct = { id, sku, title, imageUrl };
-      document.getElementById('generateBlogBtn').style.display = 'block';
-    }
-
-    async function generateBlog() {
-      if (!selectedProduct) return;
-
-      const btn = document.getElementById('generateBlogBtn');
-      btn.disabled = true;
-      btn.innerHTML = '<span class="spinner"></span> Generating...';
-      showLoading();
-
-      try {
-        const response = await fetch('/api/generate/blog', {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify(selectedProduct)
-        });
-
-        if (!response.ok) {
-          throw new Error('Failed to generate blog');
-        }
-
-        const data = await response.json();
-
-        if (data.success) {
-          showToast(\`Blog post created: "\${data.blog.title}"\`, 'success');
-          updateStats();
-          setTimeout(() => {
-            switchTab('blogs');
-            loadBlogs();
-          }, 500);
-        } else {
-          throw new Error(data.error || 'Unknown error');
-        }
-
-      } catch (error) {
-        console.error('Error generating blog:', error);
-        showToast('Failed to generate blog post. Please try again.', 'error');
-      } finally {
-        hideLoading();
-        btn.disabled = false;
-        btn.textContent = '✨ Generate Blog Post';
-      }
-    }
-
-    async function generateSocial(platform) {
-      const output = document.getElementById('socialOutput');
-      const content = document.getElementById('socialContent');
-
-      output.style.display = 'block';
-      content.textContent = 'Generating ' + platform + ' content...';
-      showLoading();
-
-      try {
-        const response = await fetch('/api/generate/social', {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify({ platform })
-        });
-
-        if (!response.ok) {
-          throw new Error('Failed to generate social content');
-        }
-
-        const data = await response.json();
-
-        if (data.success) {
-          content.textContent = data.content;
-          updateStats();
-          showToast(\`Generated \${platform} post successfully\`, 'success');
-        } else {
-          throw new Error('Generation failed');
-        }
-
-      } catch (error) {
-        console.error('Error generating social content:', error);
-        content.textContent = 'Error generating content. Please try again.';
-        showToast('Failed to generate social content', 'error');
-      } finally {
-        hideLoading();
-      }
-    }
-
-    // Function to load order insights
-    async function loadOrderInsights() {
-      showLoading();
-      try {
-        const response = await fetch('/api/shopify/orders');
-        if (!response.ok) {
-          throw new Error('Failed to fetch orders');
-        }
-
-        const data = await response.json();
-
-        if (data.success && data.orderTrends) {
-          // Update summary metrics
-          document.getElementById('totalRevenue').textContent = data.orderTrends.totalRevenue.toFixed(2);
-          document.getElementById('orderCount').textContent = data.orderTrends.orderCount;
-          document.getElementById('avgOrderValue').textContent = data.orderTrends.averageOrderValue.toFixed(2);
-
-          // Display top products
-          const topProductsDiv = document.getElementById('topProducts');
-          if (data.orderTrends.topProducts && data.orderTrends.topProducts.length > 0) {
-            topProductsDiv.innerHTML = '<table style="width: 100%; border-collapse: collapse;">' +
-              '<thead><tr style="border-bottom: 2px solid #ddd;">' +
-              '<th style="text-align: left; padding: 8px; color: #666;">Product</th>' +
-              '<th style="text-align: center; padding: 8px; color: #666;">SKU</th>' +
-              '<th style="text-align: center; padding: 8px; color: #666;">Qty Sold</th>' +
-              '</tr></thead><tbody>' +
-              data.orderTrends.topProducts.map((p, i) =>
-                `<tr style="border-bottom: 1px solid #eee;">
-                  <td style="padding: 8px; color: #333;"><strong>${i+1}.</strong> ${p.title}</td>
-                  <td style="text-align: center; padding: 8px; color: #666; font-size: 0.9em;">${p.sku}</td>
-                  <td style="text-align: center; padding: 8px; color: #f5576c; font-weight: bold;">${p.totalQuantity}</td>
-                </tr>`
-              ).join('') +
-              '</tbody></table>';
-          } else {
-            topProductsDiv.innerHTML = '<p style="color: #888;">No product data available</p>';
-          }
-
-          // Display daily revenue trend
-          const dailyRevenueDiv = document.getElementById('dailyRevenue');
-          if (data.orderTrends.dailyRevenue && data.orderTrends.dailyRevenue.length > 0) {
-            dailyRevenueDiv.innerHTML = '<table style="width: 100%; border-collapse: collapse;">' +
-              '<thead><tr style="border-bottom: 2px solid #ddd;">' +
-              '<th style="text-align: left; padding: 8px; color: #666;">Date</th>' +
-              '<th style="text-align: center; padding: 8px; color: #666;">Orders</th>' +
-              '<th style="text-align: right; padding: 8px; color: #666;">Revenue</th>' +
-              '</tr></thead><tbody>' +
-              data.orderTrends.dailyRevenue.map(d => {
-                const date = new Date(d.date);
-                const formattedDate = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
-                return `<tr style="border-bottom: 1px solid #eee;">
-                  <td style="padding: 8px; color: #333;">${formattedDate}</td>
-                  <td style="text-align: center; padding: 8px; color: #666;">${d.orderCount}</td>
-                  <td style="text-align: right; padding: 8px; color: #28a745; font-weight: bold;">$${d.revenue.toFixed(2)}</td>
-                </tr>`;
-              }).join('') +
-              '</tbody></table>';
-          } else {
-            dailyRevenueDiv.innerHTML = '<p style="color: #888;">No revenue data available</p>';
-          }
-
-          // Display recent orders
-          const ordersDiv = document.getElementById('recentOrders');
-          if (data.recentOrders && data.recentOrders.length > 0) {
-            ordersDiv.innerHTML = data.recentOrders.map(order => {
-              const orderDate = new Date(order.createdAt);
-              const items = order.lineItems.edges.map(e => e.node);
-              return `
-                <div style="background: white; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; margin-bottom: 10px;">
-                  <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 10px;">
-                    <div>
-                      <strong style="color: #f5576c;">${order.name}</strong>
-                      <span style="color: #666; margin-left: 10px; font-size: 0.9em;">
-                        ${orderDate.toLocaleDateString()} ${orderDate.toLocaleTimeString()}
-                      </span>
-                    </div>
-                    <div style="text-align: right; margin-left: auto;">
-                      <span style="font-size: 1.2em; font-weight: bold; color: #28a745;">
-                        $${parseFloat(order.totalPriceSet.shopMoney.amount).toFixed(2)}
-                      </span>
-                    </div>
-                  </div>
-                  <div style="color: #666; font-size: 0.9em;">
-                    ${items.slice(0, 3).map(item =>
-                      `• ${item.title} ${item.sku ? `(${item.sku})` : ''} × ${item.quantity}`
-                    ).join('<br>')}
-                    ${items.length > 3 ? `<br>• ... and ${items.length - 3} more items` : ''}
-                  </div>
-                  <div style="margin-top: 8px;">
-                    <span style="display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 0.8em; background: #e8f4f8; color: #0066cc;">
-                      ${order.displayFinancialStatus}
-                    </span>
-                    ${order.displayFulfillmentStatus ?
-                      `<span style="display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 0.8em; background: #f0f8e8; color: #5a8a00; margin-left: 5px;">
-                        ${order.displayFulfillmentStatus}
-                      </span>` : ''
-                    }
-                  </div>
-                </div>
-              `;
-            }).join('');
-            showToast(`Loaded ${data.recentOrders.length} recent orders`, 'success');
-          } else {
-            ordersDiv.innerHTML = '<p style="color: #888; padding: 20px;">No orders found</p>';
-          }
-
-        } else {
-          throw new Error('Invalid response format');
-        }
-
-      } catch (error) {
-        console.error('Error loading order insights:', error);
-        showToast('Failed to load order insights', 'error');
-        document.getElementById('topProducts').innerHTML = '<p style="color: #dc3545;">Error loading data</p>';
-        document.getElementById('dailyRevenue').innerHTML = '<p style="color: #dc3545;">Error loading data</p>';
-        document.getElementById('recentOrders').innerHTML = '<p style="color: #dc3545;">Error loading orders</p>';
-      } finally {
-        hideLoading();
-      }
-    }
-
-    // Load order insights on page load
-    setTimeout(() => {
-      loadOrderInsights();
-    }, 1000);
-
-    // Update stats every 10 seconds
-    setInterval(updateStats, 10000);
-    updateStats();
-  </script>
-</body>
-</html>
-  `);
-});
-
-// API endpoints
-app.get('/api/stats', requireAuth, (req, res) => {
-  res.json(stats);
-});
-
-app.post('/api/chat', requireAuth, async (req, res) => {
-  try {
-    const { message } = req.body;
-
-    if (!message) {
-      return res.status(400).json({ error: 'Message is required' });
-    }
-
-    // Memory system commands
-    if (message.toLowerCase().match(/^(remember|note|preference|learning):/i)) {
-    const parts = message.split(':');
-    const type = parts[0].toLowerCase();
-    const content = parts.slice(1).join(':').trim();
-    const memType = type === 'remember' ? 'note' : type as any;
-    agentMemory.add(content, memType);
-    return res.json({
-      response: `✅ Memory saved: "${content}"`,
-      success: true,
-      memoryAdded: true
-    });
-  }
-
-  if (message.toLowerCase().includes('show') && message.toLowerCase().includes('memor')) {
-    const summary = agentMemory.getContextSummary();
-    return res.json({ response: summary, success: true });
-  }
-
-  if (message.toLowerCase().includes('search memor')) {
-    const query = message.split(/for|about/i)[1]?.trim() || '';
-    const results = agentMemory.search(query);
-    return res.json({
-      response: results.length > 0
-        ? 'Found memories:\n' + results.map(m => `- ${m.content}`).join('\n')
-        : 'No matching memories found.',
-      success: true
-    });
-  }
-
-  if (message.toLowerCase().includes('forget') || message.toLowerCase().includes('delete memor')) {
-    const query = message.split(/forget|delete/i)[1]?.trim() || '';
-    const results = agentMemory.search(query);
-    if (results.length > 0) {
-      agentMemory.delete(results[0].id);
-      return res.json({ response: `✅ Forgot: "${results[0].content}"`, success: true });
-    }
-    return res.json({ response: 'Nothing found to forget.', success: true });
-  }
-
-    stats.totalConversations++;
-
-    if (!req.session.chatHistory) {
-      req.session.chatHistory = [];
-    }
-
-    req.session.chatHistory.push({ role: 'user', content: message });
-
-    // Parse the message for product-specific requests
-    let productContext = '';
-    const productMatch = message.match(/product\s+([^,\s]+)|vendor\s+([^,\s]+)|sku\s+([^,\s]+)/i);
-
-    if (productMatch) {
-      // Search for the product in Shopify
-      const searchTerm = productMatch[1] || productMatch[2] || productMatch[3];
-      try {
-        const query = `query {
-          products(first: 5, query: "${searchTerm}") {
-            edges {
-              node {
-                id
-                title
-                handle
-                vendor
-                description
-                variants(first: 1) {
-                  edges {
-                    node {
-                      sku
-                      price
-                    }
-                  }
-                }
-                featuredImage {
-                  url
-                  altText
-                }
-                images(first: 5) {
-                  edges {
-                    node {
-                      url
-                      altText
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }`;
-        const productData = await shopifyGraphQL(query);
-
-        if (productData.data?.products?.edges?.length > 0) {
-          const products = productData.data.products.edges.map((edge: any) => ({
-            title: edge.node.title,
-            vendor: edge.node.vendor,
-            sku: edge.node.variants.edges[0]?.node.sku || 'N/A',
-            price: edge.node.variants.edges[0]?.node.price || 'N/A',
-            description: edge.node.description,
-            mainImage: edge.node.featuredImage?.url || '',
-            images: edge.node.images.edges.map((img: any) => img.node.url),
-            shopifyUrl: `https://${SHOPIFY_STORE}/products/${edge.node.handle}`
-          }));
-
-          productContext = `\n\nFOUND PRODUCTS:\n${products.map(p =>
-            `- ${p.title} by ${p.vendor} (SKU: ${p.sku})
-  Price: $${p.price}
-  Description: ${p.description?.substring(0, 200)}...
-  Main Image: ${p.mainImage}
-  All Images: ${p.images.join(', ')}
-  Product URL: ${p.shopifyUrl}`
-          ).join('\n\n')}`;
-        }
-      } catch (err) {
-        console.error('Error searching products:', err);
-      }
-    }
-
-    const systemPrompt = `You are the Marketing Agent for Designer Wallcoverings, a specialist in creating Architectural Digest-style editorial content.
-
-Your responsibilities:
-1. Create compelling, magazine-quality blog posts from product SKUs
-2. Generate social media content (Instagram, Facebook, Pinterest)
-3. Draft email marketing campaigns
-4. Write engaging product descriptions in professional editorial style
-5. Provide marketing strategy advice
-
-BLOG POST CREATION SKILLS:
-When asked to create a blog post for a product, you should:
-- Write in the sophisticated, editorial style of Architectural Digest
-- Include proper HTML structure with headings, paragraphs, and semantic markup
-- Reference product images with full URLs in <img> tags and proper alt text
-- Link to the product page using the provided Shopify URL with descriptive anchor text
-- Describe the product's design, texture, color, and styling possibilities
-- Reference interior design trends and room applications
-- Use designer terminology and sophisticated language
-- Include multiple product images throughout the post
-- Add proper meta description and SEO keywords
-- Structure: Opening hook → Product details → Design applications → Styling tips → Call-to-action
-
-PRODUCT DATA AVAILABLE:${productContext}
-
-You have access to:
-- Shopify store with products and blog posts
-- Product details including images, descriptions, SKUs, prices, vendors
-- Direct product URLs for linking
-
-Current statistics:
-- Blog posts created: ${stats.blogPostsCreated}
-- Social posts generated: ${stats.socialPostsGenerated}
-- Email campaigns: ${stats.emailCampaignsDrafted}
-
-Recent blog posts:
-${stats.recentBlogs.length > 0 ? stats.recentBlogs.slice(-3).map(blog =>
-  `- "${blog.title}" (created ${new Date(blog.createdAt).toLocaleDateString()})`
-).join('\n') : '- No recent blogs'}
-
-${agentMemory.getContextSummary()}
-
-When creating content:
-1. Use sophisticated, editorial writing style
-2. Include actual image URLs from the product data
-3. Create proper HTML with href links to product pages
-4. Reference specific product details (SKU, vendor, price)
-5. Write compelling, benefit-focused copy
-6. Use SEO-optimized headlines and descriptions
-7. Reference stored memories and preferences when relevant
-
-Be professional, sophisticated, and magazine-editorial in your approach.`;
-
-    const response = await anthropic.messages.create({
-      model: 'claude-sonnet-4-20250514',
-      max_tokens: 4096, // Increased for longer blog posts
-      system: systemPrompt,
-      messages: req.session.chatHistory
-    });
-
-    const assistantMessage = response.content[0].type === 'text'
-      ? response.content[0].text
-      : 'I apologize, but I encountered an error.';
-
-    req.session.chatHistory.push({ role: 'assistant', content: assistantMessage });
-
-    if (req.session.chatHistory.length > 20) {
-      req.session.chatHistory = req.session.chatHistory.slice(-20);
-    }
-
-    res.json({ response: assistantMessage });
-
-  } catch (error) {
-    console.error('Chat error:', error);
-    res.status(500).json({
-      error: 'Failed to process message',
-      response: 'I apologize, but I encountered an error. Please try again.'
-    });
-  }
-});
-
-// Shopify blog posts
-app.get('/api/shopify/blogs', requireAuth, async (req, res) => {
-  try {
-    const query = `
-      query {
-        articles(first: 10, sortKey: CREATED_AT, reverse: true) {
-          edges {
-            node {
-              id
-              title
-              createdAt
-              publishedAt
-              tags
-              handle
-            }
-          }
-        }
-      }
-    `;
-
-    const data = await shopifyGraphQL(query);
-
-    if (data.data && data.data.articles) {
-      const blogs = data.data.articles.edges.map((edge: any) => ({
-        id: edge.node.id,
-        title: edge.node.title,
-        createdAt: edge.node.createdAt,
-        publishedAt: edge.node.publishedAt,
-        tags: edge.node.tags.join(', '),
-        handle: edge.node.handle
-      }));
-
-      stats.recentBlogs = blogs;
-      res.json({ blogs });
-    } else {
-      res.json({ blogs: [] });
-    }
-
-  } catch (error) {
-    console.error('Error fetching blogs:', error);
-    res.status(500).json({ error: 'Failed to fetch blogs' });
-  }
-});
-
-// Shopify products
-app.get('/api/shopify/products', requireAuth, async (req, res) => {
-  try {
-    const query = `
-      query {
-        products(first: 20, sortKey: CREATED_AT, reverse: true) {
-          edges {
-            node {
-              id
-              title
-              handle
-              vendor
-              variants(first: 1) {
-                edges {
-                  node {
-                    sku
-                  }
-                }
-              }
-              featuredImage {
-                url
-              }
-            }
-          }
-        }
-      }
-    `;
-
-    const data = await shopifyGraphQL(query);
-
-    if (data.data && data.data.products) {
-      const products = data.data.products.edges.map((edge: any) => ({
-        id: edge.node.id,
-        title: edge.node.title,
-        handle: edge.node.handle,
-        vendor: edge.node.vendor,
-        sku: edge.node.variants.edges[0]?.node.sku || 'N/A',
-        imageUrl: edge.node.featuredImage?.url || ''
-      }));
-
-      stats.recentProducts = products;
-      res.json({ products });
-    } else {
-      res.json({ products: [] });
-    }
-
-  } catch (error) {
-    console.error('Error fetching products:', error);
-    res.status(500).json({ error: 'Failed to fetch products' });
-  }
-});
-
-// Generate blog post from product
-app.post('/api/generate/blog', requireAuth, async (req, res) => {
-  try {
-    const { id, sku, title, imageUrl } = req.body;
-
-    // Use Claude to generate blog content
-    const prompt = `Create a compelling blog post for this wallcovering product:
-
-Product Title: ${title}
-SKU: ${sku}
-
-Write an engaging blog post (300-500 words) that includes:
-1. An attention-grabbing headline
-2. Introduction highlighting the product's unique features
-3. Design trends and styling suggestions
-4. Room application ideas
-5. Call-to-action
-
-Make it SEO-friendly and engaging for interior designers and homeowners.`;
-
-    const response = await anthropic.messages.create({
-      model: 'claude-sonnet-4-20250514',
-      max_tokens: 2048,
-      messages: [{ role: 'user', content: prompt }]
-    });
-
-    const blogContent = response.content[0].type === 'text' ? response.content[0].text : '';
-
-    // Extract title (first line) and body
-    const lines = blogContent.split('\n');
-    const blogTitle = lines[0].replace(/^#+ /, '').trim();
-    const blogBody = lines.slice(1).join('\n').trim();
-
-    stats.blogPostsCreated++;
-    stats.lastBlogPost = {
-      title: blogTitle,
-      sku,
-      timestamp: new Date()
-    };
-
-    // In production, would create blog post via Shopify API
-    res.json({
-      success: true,
-      blog: {
-        title: blogTitle,
-        content: blogBody,
-        sku,
-        productId: id
-      }
-    });
-
-  } catch (error) {
-    console.error('Error generating blog:', error);
-    res.status(500).json({ error: 'Failed to generate blog post' });
-  }
-});
-
-// Generate social media content
-app.post('/api/generate/social', requireAuth, async (req, res) => {
-  try {
-    const { platform } = req.body;
-
-    const prompts: Record<string, string> = {
-      instagram: 'Write an engaging Instagram caption (150 chars) for a new designer wallcovering collection. Include emojis and 5 relevant hashtags.',
-      facebook: 'Write a Facebook post (200 chars) announcing a new wallcovering line. Make it informative and include a call-to-action.',
-      pinterest: 'Write a Pinterest pin description (300 chars) for wallcovering design inspiration. Focus on style and room transformation.'
-    };
-
-    const response = await anthropic.messages.create({
-      model: 'claude-sonnet-4-20250514',
-      max_tokens: 512,
-      messages: [{ role: 'user', content: prompts[platform] || prompts.instagram }]
-    });
-
-    const content = response.content[0].type === 'text' ? response.content[0].text : '';
-
-    stats.socialPostsGenerated++;
-
-    res.json({
-      success: true,
-      platform,
-      content
-    });
-
-  } catch (error) {
-    console.error('Error generating social content:', error);
-    res.status(500).json({ error: 'Failed to generate social content' });
-  }
-});
-
-// Shopify orders endpoint for marketing insights
-app.get('/api/shopify/orders', requireAuth, async (req, res) => {
-  try {
-    // Fetch and analyze orders
-    const orders = await fetchAndAnalyzeOrders();
-
-    res.json({
-      success: true,
-      recentOrders: orders,
-      orderTrends: stats.orderTrends,
-      summary: {
-        totalOrders: orders.length,
-        lastUpdated: new Date().toISOString()
-      }
-    });
-
-  } catch (error) {
-    console.error('Error fetching orders:', error);
-    res.status(500).json({
-      error: 'Failed to fetch order data',
-      success: false
-    });
-  }
-});
-
-app.listen(PORT, '0.0.0.0', () => {
-  console.log('');
-  console.log('📢 Marketing Agent');
-  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
-  console.log('🌍 External: http://45.61.58.125:' + PORT);
-  console.log('🏠 Local: http://localhost:' + PORT);
-  console.log('');
-  console.log('💬 Chat Mode: Active');
-  console.log('📝 Shopify Blog Integration: Ready');
-  console.log('✨ AI Content Generation: Active');
-  console.log('📱 Social Media Tools: Ready');
-  console.log('📧 Email Campaigns: Ready');
-  console.log('');
-  console.log('✅ Marketing Agent ready for content creation...');
-});

← 6c0fe229 auto-data-snapshot: 2026-09-17T15:48:34 (3 data files) — sho  ·  back to Designer Wallcoverings  ·  harden: escape img src in dw-boost-overrides cardHtml (was r 9d1c6b1f →