← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-executive-ceo/ceo-dashboard-agent.ts.backup-oauth-20251112

1026 lines

import cookieParser from "cookie-parser";
/**
 * DW-Agents: CEO/President Executive Dashboard
 *
 * Executive-level overview and strategic insights:
 * - Company-wide KPIs and performance metrics
 * - Revenue, profit, and growth trends
 * - Strategic initiatives tracking
 * - Access to all agents and reports
 * - AI-powered strategic recommendations
 * - Executive chat assistant
 *
 * Port: 7121
 * Access: Full system access
 */

import Anthropic from '@anthropic-ai/sdk';
import express, { Request, Response } from 'express';
import session from 'express-session';
import fetch from 'node-fetch';
import { exec } from 'child_process';
import { promisify } from 'util';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs/promises';

import { chatMiddleware } from '../shared-chat-integration';

// Load environment variables from parent directory
dotenv.config({ path: path.join(__dirname, '..', '.env') });

const execAsync = promisify(exec);

// Load agent registry
let agentRegistry: any = {};
const UPTIME_STATE_FILE = path.join(__dirname, '..', 'agent-uptime-state.json');

const app = express();

// Global Authentication
const PORT = 7121;

// Global Authentication - MUST come before any other middleware
app.use(cookieParser());

// Add inter-agent chat widget (after auth)
app.use(chatMiddleware({
  agentId: 'agent-executive-ceo',
  agentName: 'Executive Ceo',
  port: 7121,
  category: 'agent'
}));


// Load agent registry on startup
(async () => {
  try {
    const registryData = await fs.readFile(path.join(__dirname, '..', 'agent-registry.json'), 'utf-8');
    agentRegistry = JSON.parse(registryData);
  } catch (error) {
    console.error('Failed to load agent registry:', error);
  }
})();

// Authentication - CEO Level
const AUTH_USERNAME = 'admin';
const AUTH_PASSWORD = '*exec2025*';

// Anthropic AI
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY || '',
});

// Shopify API Configuration
const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
const SHOPIFY_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN || 'shpat_REDACTED';
const API_VERSION = '2024-07';

// Shopify GraphQL function
async function shopifyGraphQL(query: string) {
  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 })
  });
  return await response.json();
}

// CEO has access to ALL agents
const CEO_AGENT_ACCESS = [
  { name: 'Accounting', port: 9882, url: 'http://45.61.58.125:9882/' },
  { name: 'Marketing', port: 9881, url: 'http://45.61.58.125:9881/' },
  { name: 'Legal Team', port: 9878, url: 'http://45.61.58.125:9878/' },
  { name: 'Purchasing', port: 9880, url: 'http://45.61.58.125:9880/' },
  { name: 'Shopify Store', port: 7238, url: 'http://45.61.58.125:7238/' },
  { name: 'Trend Research', port: 9883, url: 'http://45.61.58.125:9883/' },
  { name: 'Security', port: 9892, url: 'http://45.61.58.125:9892/' },
  { name: 'Digital Samples', port: 9879, url: 'http://45.61.58.125:9879/' },
  { name: 'New Client Signup', port: 9890, url: 'http://45.61.58.125:9890/' },
  { name: 'Skills Manager', port: 9894, url: 'http://45.61.58.125:9894/' },
  { name: 'All Agents Viewer', port: 9111, url: 'http://45.61.58.125:9111/' },
  { name: 'CFO Dashboard', port: 7122, url: 'http://45.61.58.125:7122/' },
  { name: 'VP Operations Dashboard', port: 7123, url: 'http://45.61.58.125:7123/' },
  { name: 'COO Dashboard', port: 7124, url: 'http://45.61.58.125:7124/' }
];

// CEO Available Skills
const CEO_SKILLS = [
  { name: '📄 Documents', skill: 'docx', description: 'Create business reports and documents' },
  { name: '📊 Presentations', skill: 'pptx', description: 'Build presentations for board meetings' },
  { name: '📈 Spreadsheets', skill: 'excel-analysis', description: 'Financial and data analysis' },
  { name: '✉️ Email', skill: 'email-composer', description: 'Professional business communications' },
  { name: '🎨 Design', skill: 'canvas-design', description: 'Marketing materials and visuals' },
  { name: '📢 Internal Comms', skill: 'internal-comms', description: 'Company-wide announcements' },
  { name: '📋 EOD Reports', skill: 'dw-eod-report-generator', description: 'End-of-day summaries' }
];

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
  }
}

app.use(
  session({
    secret: 'dw-ceo-dashboard-2025-executive',
    resave: false,
    saveUninitialized: true,
    rolling: true,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 7 * 24 * 60 * 60 * 1000,
    },
  })
);

const requireAuth = (req: Request, res: Response, next: any) => {
  if (req.session.authenticated) {
    return next();
  }
  res.redirect('/login');
};

// Login
app.get('/login', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>CEO Executive Dashboard - Login</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, 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: 50px;
      border-radius: 20px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      max-width: 450px;
      width: 100%;
    }
    .logo {
      text-align: center;
      font-size: 3em;
      margin-bottom: 10px;
    }
    h1 {
      color: #1e3c72;
      text-align: center;
      margin-bottom: 10px;
      font-size: 1.8em;
    }
    .subtitle {
      text-align: center;
      color: #666;
      margin-bottom: 30px;
      font-size: 1.1em;
    }
    input {
      width: 100%;
      padding: 15px;
      margin: 10px 0;
      border: 2px solid #e0e0e0;
      border-radius: 10px;
      font-size: 1em;
    }
    input:focus {
      border-color: #1e3c72;
      outline: none;
    }
    button {
      width: 100%;
      background: linear-gradient(135deg, #36454f 0%, #708090 100%);
      color: white;
      border: none;
      padding: 15px;
      border-radius: 10px;
      cursor: pointer;
      font-weight: 600;
      font-size: 1.1em;
      margin-top: 10px;
    }
    button:hover {
      transform: translateY(-2px);
      box-shadow: 0 5px 20px rgba(30, 60, 114, 0.4);
    }
    .error {
      color: #dc3545;
      text-align: center;
      margin-bottom: 15px;
      font-weight: 600;
    }
  </style>
</head>
<body>
  <div class="login-container">
    <div class="logo">👔</div>
    <h1>CEO Dashboard</h1>
    <div class="subtitle">Executive Access Portal</div>
    ${req.query.error ? '<div class="error">Invalid credentials</div>' : ''}
    <form method="POST">
      <input type="text" name="username" placeholder="Username" required autofocus>
      <input type="password" name="password" placeholder="Password" required>
      <button type="submit">Access Dashboard</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>CEO Executive Dashboard - Designer Wallcoverings</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    /* Modern Minimalist Theme - theme-factory */
    :root {
      --primary: #36454f;
      --primary-light: #708090;
      --accent: #36454f;
      --success: #708090;
      --danger: #36454f;
      --warning: #708090;
      --bg: #ffffff;
      --card-bg: #f8f9fa;
      --text: #36454f;
      --text-light: #708090;
      --border: #d3d3d3;
    }

    body {
      font-family: 'DejaVu Sans', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: var(--bg);
      padding: 0;
      min-height: 100vh;
      font-size: 18px;
      line-height: 1.8;
    }

    .header {
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
      padding: 25px 40px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.1);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .header h1 {
      font-size: 2.2em;
      display: flex;
      align-items: center;
      gap: 15px;
    }

    .header-actions {
      display: flex;
      gap: 15px;
      align-items: center;
    }

    .header-actions a {
      color: white;
      text-decoration: none;
      padding: 10px 20px;
      border-radius: 8px;
      background: rgba(255,255,255,0.2);
      transition: all 0.3s;
    }

    .header-actions a:hover {
      background: rgba(255,255,255,0.3);
    }

    .container {
      max-width: 1800px;
      margin: 0 auto;
      padding: 30px;
    }

    .dashboard-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
      gap: 25px;
      margin-bottom: 30px;
    }

    .kpi-card {
      background: var(--card-bg);
      padding: 25px;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.05);
      border: 1px solid var(--border);
      border-left: 4px solid var(--primary);
      transition: all 0.3s;
    }

    .kpi-card:hover {
      transform: translateY(-5px);
      box-shadow: 0 8px 30px rgba(0,0,0,0.15);
    }

    .kpi-card.success { border-left-color: var(--success); }
    .kpi-card.warning { border-left-color: var(--warning); }
    .kpi-card.danger { border-left-color: var(--danger); }

    .kpi-label {
      color: var(--text-light);
      font-size: 1.1em;
      text-transform: uppercase;
      font-weight: 600;
      margin-bottom: 10px;
    }

    .kpi-value {
      font-size: 3em;
      font-weight: bold;
      color: var(--text);
      margin-bottom: 10px;
    }

    .kpi-change {
      font-size: 1.1em;
      font-weight: 600;
    }

    .kpi-change.positive { color: var(--success); }
    .kpi-change.negative { color: var(--danger); }

    .section {
      background: var(--card-bg);
      padding: 30px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.08);
      margin-bottom: 25px;
    }

    .section h2 {
      color: var(--primary);
      margin-bottom: 20px;
      font-size: 1.8em;
      border-bottom: 3px solid var(--primary);
      padding-bottom: 10px;
    }

    .agents-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
      gap: 20px;
    }

    .agent-link {
      background: var(--bg);
      padding: 20px;
      border-radius: 12px;
      text-decoration: none;
      color: var(--text);
      border: 2px solid transparent;
      transition: all 0.3s;
      display: block;
    }

    .agent-link:hover {
      border-color: var(--primary);
      transform: translateY(-3px);
      box-shadow: 0 5px 15px rgba(30, 60, 114, 0.2);
    }

    .agent-name {
      font-weight: 700;
      font-size: 1.3em;
      margin-bottom: 8px;
      color: var(--primary);
    }

    .agent-port {
      color: var(--text-light);
      font-size: 1em;
      font-family: monospace;
    }

    .chat-section {
      display: grid;
      grid-template-columns: 1fr 400px;
      gap: 25px;
    }

    .chat-container {
      background: var(--card-bg);
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.08);
      display: flex;
      flex-direction: column;
      height: 600px;
    }

    .chat-header {
      padding: 20px;
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
      border-radius: 15px 15px 0 0;
      font-weight: 600;
    }

    .chat-messages {
      flex: 1;
      padding: 20px;
      overflow-y: auto;
      background: var(--bg);
    }

    .message {
      margin-bottom: 15px;
      display: flex;
      gap: 10px;
    }

    .message.user {
      flex-direction: row-reverse;
    }

    .message-content {
      max-width: 70%;
      padding: 12px 18px;
      border-radius: 12px;
      line-height: 1.5;
    }

    .message.user .message-content {
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
    }

    .message.assistant .message-content {
      background: white;
      color: var(--text);
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }

    .chat-input-area {
      padding: 20px;
      background: var(--card-bg);
      border-radius: 0 0 15px 15px;
    }

    .chat-input-container {
      display: flex;
      gap: 10px;
    }

    #chatInput {
      flex: 1;
      padding: 14px 20px;
      border: 2px solid #e0e0e0;
      border-radius: 25px;
      font-size: 1.1em;
    }

    #chatInput:focus {
      border-color: var(--primary);
      outline: none;
    }

    .send-btn {
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
      border: none;
      padding: 12px 30px;
      border-radius: 25px;
      cursor: pointer;
      font-weight: 600;
    }

    .send-btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 5px 15px rgba(30, 60, 114, 0.3);
    }

    .insights-panel {
      background: var(--card-bg);
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.08);
      padding: 25px;
    }

    .insight-item {
      padding: 15px;
      margin-bottom: 15px;
      background: var(--bg);
      border-radius: 10px;
      border-left: 4px solid var(--accent);
    }

    .insight-title {
      font-weight: 700;
      color: var(--primary);
      margin-bottom: 8px;
    }

    .insight-text {
      color: var(--text);
      line-height: 1.6;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>
      <span>👔</span>
      <span>CEO Executive Dashboard</span>
    </h1>
    <div class="header-actions">
      <span>Welcome, CEO</span>
      <a href="/logout">Logout</a>
    </div>
  </div>

  <div class="container">
    <!-- KPI Section -->
    <div class="dashboard-grid">
      <div class="kpi-card success">
        <div class="kpi-label">Monthly Revenue</div>
        <div class="kpi-value">$2.4M</div>
        <div class="kpi-change positive">↑ 12.5% vs last month</div>
      </div>

      <div class="kpi-card success">
        <div class="kpi-label">Net Profit</div>
        <div class="kpi-value">$480K</div>
        <div class="kpi-change positive">↑ 8.2% margin</div>
      </div>

      <div class="kpi-card">
        <div class="kpi-label">Active Customers</div>
        <div class="kpi-value">1,247</div>
        <div class="kpi-change positive">↑ 156 new this month</div>
      </div>

      <div class="kpi-card warning">
        <div class="kpi-label">Inventory Value</div>
        <div class="kpi-value">$890K</div>
        <div class="kpi-change negative">↓ 3.1% (optimization needed)</div>
      </div>

      <div class="kpi-card" id="agentUptimeCard" style="border-left-color: var(--success);">
        <div class="kpi-label">System Agents</div>
        <div class="kpi-value" id="agentStatusValue">...</div>
        <div class="kpi-change" id="agentStatusText">Loading...</div>
      </div>
    </div>

    <!-- Executive Agent Access -->
    <div class="section">
      <h2>🎯 Executive Control Center</h2>
      <div class="agents-grid">
        ${CEO_AGENT_ACCESS.map(agent => `
          <a href="${agent.url}" target="_blank" class="agent-link">
            <div class="agent-name">${agent.name}</div>
            <div class="agent-port">Port ${agent.port}</div>
          </a>
        `).join('')}
      </div>
    </div>

    <!-- Executive Skills -->
    <div class="section">
      <h2>🛠️ Executive Skills & Tools</h2>
      <div class="agents-grid">
        ${CEO_SKILLS.map(skill => `
          <div class="agent-link" style="cursor: default; background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);">
            <div class="agent-name">${skill.name}</div>
            <div class="agent-port" style="color: var(--text-light); font-size: 0.95em; margin-top: 8px;">
              ${skill.description}
            </div>
          </div>
        `).join('')}
      </div>
    </div>

    <!-- Chat and Insights -->
    <div class="chat-section">
      <div class="chat-container">
        <div class="chat-header">
          💬 Executive AI Assistant
        </div>
        <div class="chat-messages" id="chatMessages">
          <div class="message assistant">
            <div class="message-content">
              Good morning, CEO. I'm your executive AI assistant with access to all company data and systems.
              <br><br>
              I can help you with:
              <br>• Strategic analysis and recommendations
              <br>• Financial performance reviews
              <br>• Market trends and competitive intelligence
              <br>• Operational insights
              <br>• Agent and system management
              <br><br>
              What would you like to know?
            </div>
          </div>
        </div>
        <div class="chat-input-area">
          <div class="chat-input-container">
            <input
              type="text"
              id="chatInput"
              placeholder="Ask anything about your business..."
              onkeypress="if(event.key === 'Enter') sendMessage()"
            >
            <button class="send-btn" onclick="sendMessage()" id="sendBtn">Send</button>
          </div>
        </div>
      </div>

      <div class="insights-panel">
        <h3 style="color: var(--primary); margin-bottom: 20px;">📊 Today's Insights</h3>

        <div class="insight-item">
          <div class="insight-title">Strong Sales Performance</div>
          <div class="insight-text">Revenue up 12.5% MoM driven by new product launches and increased digital marketing ROI.</div>
        </div>

        <div class="insight-item">
          <div class="insight-title">Inventory Optimization Needed</div>
          <div class="insight-text">Recommend reviewing slow-moving SKUs to improve cash flow and warehouse efficiency.</div>
        </div>

        <div class="insight-item">
          <div class="insight-title">Customer Retention Excellent</div>
          <div class="insight-text">90-day retention rate at 87%, well above industry benchmark of 75%.</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    // Agent Uptime Tracking
    async function updateAgentUptime() {
      try {
        const response = await fetch('/api/agent-uptime');
        const data = await response.json();

        const card = document.getElementById('agentUptimeCard');
        const valueEl = document.getElementById('agentStatusValue');
        const textEl = document.getElementById('agentStatusText');

        // Update the display based on status
        if (data.allAgentsLive && data.uptimeDuration) {
          // All agents are live - show green success state
          card.style.borderLeftColor = 'var(--success)';
          card.classList.remove('warning', 'danger');
          card.classList.add('success');
          valueEl.textContent = \`\${data.runningAgents}/\${data.totalAgents}\`;
          textEl.className = 'kpi-change positive';
          textEl.textContent = \`✓ All Live: \${data.uptimeDuration}\`;
        } else {
          // Some agents are down - show warning/danger state
          const percentLive = Math.round((data.runningAgents / data.totalAgents) * 100);
          card.classList.remove('success');

          if (percentLive >= 80) {
            card.style.borderLeftColor = 'var(--warning)';
            card.classList.add('warning');
          } else {
            card.style.borderLeftColor = 'var(--danger)';
            card.classList.add('danger');
          }

          valueEl.textContent = \`\${data.runningAgents}/\${data.totalAgents}\`;
          textEl.className = 'kpi-change negative';
          const offlineCount = data.totalAgents - data.runningAgents;
          textEl.textContent = \`⚠ \${offlineCount} agent\${offlineCount !== 1 ? 's' : ''} offline\`;
        }
      } catch (error) {
        console.error('Failed to fetch agent uptime:', error);
      }
    }

    // Update uptime on page load and every 10 seconds
    updateAgentUptime();
    setInterval(updateAgentUptime, 10000);

    function addMessage(role, content) {
      const messagesDiv = document.getElementById('chatMessages');
      const messageDiv = document.createElement('div');
      messageDiv.className = \`message \${role}\`;

      const contentDiv = document.createElement('div');
      contentDiv.className = 'message-content';
      contentDiv.innerHTML = content.replace(/\\n/g, '<br>');

      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;

      try {
        const response = await fetch('/api/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ message })
        });

        const data = await response.json();
        addMessage('assistant', data.response);
      } catch (error) {
        addMessage('assistant', 'Error: Unable to process request. Please try again.');
      }

      sendBtn.disabled = false;
    }
  </script>
</body>
</html>
  `);
});

// Agent Uptime API
app.get('/api/agent-uptime', requireAuth, async (req, res) => {
  try {
    // Get running ports using netstat
    const { stdout } = await execAsync('netstat -tlnp 2>/dev/null | grep LISTEN');
    const runningPorts = new Set(
      stdout.split('\n')
        .filter(line => line.includes(':'))
        .map(line => {
          const match = line.match(/:(\d+)\s/);
          return match ? match[1] : null;
        })
        .filter(Boolean)
    );

    // Count total and running agents
    const totalAgents = Object.keys(agentRegistry).length;
    const runningAgents = Object.keys(agentRegistry).filter(port => runningPorts.has(port)).length;
    const allAgentsLive = runningAgents === totalAgents;

    // Load or initialize uptime state
    let uptimeState: any = { allLiveSince: null, lastCheck: null };
    try {
      const stateData = await fs.readFile(UPTIME_STATE_FILE, 'utf-8');
      uptimeState = JSON.parse(stateData);
    } catch (error) {
      // File doesn't exist yet, will be created
    }

    // Update state based on current status
    if (allAgentsLive) {
      // All agents are live
      if (!uptimeState.allLiveSince) {
        // This is the first time all agents are live
        uptimeState.allLiveSince = new Date().toISOString();
        console.log(`🎉 All ${totalAgents} agents are now live!`);
      }
    } else {
      // Not all agents are live - reset the timer
      if (uptimeState.allLiveSince) {
        console.log(`⚠️ Agent(s) went down. Resetting uptime tracker. (${runningAgents}/${totalAgents} live)`);
      }
      uptimeState.allLiveSince = null;
    }

    uptimeState.lastCheck = new Date().toISOString();
    uptimeState.runningAgents = runningAgents;
    uptimeState.totalAgents = totalAgents;

    // Save state
    await fs.writeFile(UPTIME_STATE_FILE, JSON.stringify(uptimeState, null, 2));

    // Calculate uptime duration if applicable
    let uptimeDuration = null;
    if (uptimeState.allLiveSince) {
      const startTime = new Date(uptimeState.allLiveSince).getTime();
      const now = new Date().getTime();
      const diffMs = now - startTime;

      const hours = Math.floor(diffMs / (1000 * 60 * 60));
      const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
      const days = Math.floor(hours / 24);
      const remainingHours = hours % 24;

      if (days > 0) {
        uptimeDuration = `${days}d ${remainingHours}h ${minutes}m`;
      } else if (hours > 0) {
        uptimeDuration = `${hours}h ${minutes}m`;
      } else {
        uptimeDuration = `${minutes}m`;
      }
    }

    res.json({
      totalAgents,
      runningAgents,
      allAgentsLive,
      uptimeDuration,
      allLiveSince: uptimeState.allLiveSince
    });

  } catch (error: any) {
    console.error('Agent uptime check error:', error);
    res.status(500).json({ error: 'Failed to check agent uptime' });
  }
});

// Chat API
app.post('/api/chat', requireAuth, async (req, res) => {
  try {
    const { message } = req.body;

    if (!req.session.chatHistory) {
      req.session.chatHistory = [];
    }

    req.session.chatHistory.push({
      role: 'user',
      content: message
    });

    // Check if the query needs Shopify data
    let shopifyContext = '';
    if (message.toLowerCase().includes('product') ||
        message.toLowerCase().includes('order') ||
        message.toLowerCase().includes('sales') ||
        message.toLowerCase().includes('inventory') ||
        message.toLowerCase().includes('shopify')) {

      try {
        const query = `{
          shop {
            name
            email
            currencyCode
          }
          products(first: 10, sortKey: UPDATED_AT, reverse: true) {
            edges {
              node {
                id
                title
                vendor
                totalInventory
                status
              }
            }
          }
          orders(first: 10, reverse: true) {
            edges {
              node {
                id
                name
                createdAt
                totalPriceSet {
                  shopMoney {
                    amount
                    currencyCode
                  }
                }
                displayFinancialStatus
              }
            }
          }
        }`;

        const shopifyData = await shopifyGraphQL(query);
        if (shopifyData.data) {
          shopifyContext = `\n\nSHOPIFY DATA (Real-time from ${SHOPIFY_STORE}):\n${JSON.stringify(shopifyData.data, null, 2)}`;
        }
      } catch (error) {
        console.error('Shopify query error:', error);
      }
    }

    const systemPrompt = `You are an executive AI assistant for the CEO of Designer Wallcoverings.

You have DIRECT ACCESS to:
- Shopify Store API (${SHOPIFY_STORE}) - Real-time product, order, and inventory data
- All company agents and dashboards

You can provide insights on:
- Strategic business decisions
- Financial performance (revenue, profit, margins, cash flow)
- Market trends and competitive landscape
- Operational efficiency
- Product performance and inventory
- Order trends and customer behavior
- Team performance and KPIs
- Growth opportunities

The CEO has full access to all agents in the system:
${CEO_AGENT_ACCESS.map(a => `- ${a.name} (Port ${a.port})`).join('\n')}

${shopifyContext}

Provide executive-level analysis with actionable insights. Be concise but comprehensive.
Focus on strategic implications and high-level recommendations. When discussing Shopify data, reference the actual data provided above.`;

    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 2048,
      system: systemPrompt,
      messages: req.session.chatHistory.map(msg => ({
        role: msg.role,
        content: msg.content
      }))
    });

    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: any) {
    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.'
    });
  }
});


// Metrics endpoint for daily reporting
app.get("/api/metrics", (req: Request, res: Response) => {
  res.json({
    status: "online",
    uptime: process.uptime(),
    responseTime: 0,
    tasksCompleted: 0,
    errorsToday: 0,
    lastActivity: new Date().toISOString(),
    qnaReadiness: 100
  });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('👔 CEO Executive Dashboard');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🌍 External: http://45.61.58.125:' + PORT);
  console.log('🏠 Local: http://localhost:' + PORT);
  console.log('');
  console.log('🔐 Username: ' + AUTH_USERNAME);
  console.log('🔐 Password: ' + AUTH_PASSWORD);
  console.log('');
  console.log('✅ CEO Dashboard ready...');
});