[object Object]

← back to Dw Boardroom V2

initial scaffold (gitify-all 2026-05-06)

82c61294cec09d803d787f6438e3dbf04ed537cf · 2026-05-06 10:25:15 -0700 · Steve Abrams

Files touched

Diff

commit 82c61294cec09d803d787f6438e3dbf04ed537cf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 10:25:15 2026 -0700

    initial scaffold (gitify-all 2026-05-06)
---
 .gitignore                                  |   16 +
 boardroom.db-shm                            |  Bin 0 -> 32768 bytes
 boardroom.db-wal                            |  Bin 0 -> 3563832 bytes
 ecosystem.config.cjs                        |   29 +
 frontend/index.html                         |   42 +
 frontend/package-lock.json                  | 1846 +++++++++++++++++++++++++++
 frontend/package.json                       |   22 +
 frontend/serve.cjs                          |   43 +
 frontend/src/App.tsx                        |  108 ++
 frontend/src/api.ts                         |   47 +
 frontend/src/components/AgendaManager.tsx   |  132 ++
 frontend/src/components/AgentRoster.tsx     |  125 ++
 frontend/src/components/LiveFeed.tsx        |   77 ++
 frontend/src/components/MeetingControls.tsx |   74 ++
 frontend/src/components/MeetingRoom.tsx     |   86 ++
 frontend/src/components/PhaseBar.tsx        |   68 +
 frontend/src/hooks/useApi.ts                |   20 +
 frontend/src/hooks/useWebSocket.ts          |   54 +
 frontend/src/main.tsx                       |    9 +
 frontend/src/types.ts                       |   62 +
 frontend/tsconfig.json                      |   21 +
 frontend/vite.config.ts                     |   22 +
 package-lock.json                           | 1626 +++++++++++++++++++++++
 package.json                                |   31 +
 src/agents/president.ts                     |   58 +
 src/agents/registry.ts                      |  148 +++
 src/agents/voices.ts                        |   79 ++
 src/api/middleware/auth.ts                  |   25 +
 src/api/routes/agenda.ts                    |   65 +
 src/api/routes/agents.ts                    |   67 +
 src/api/routes/health.ts                    |   45 +
 src/api/routes/meetings.ts                  |  121 ++
 src/api/routes/voice.ts                     |   51 +
 src/api/server.ts                           |   79 ++
 src/db/migrate.ts                           |   37 +
 src/db/schema.sql                           |   58 +
 src/db/sqlite.ts                            |   45 +
 src/engine/agentDialogue.ts                 |  153 +++
 src/engine/governanceClient.ts              |  128 ++
 src/engine/meetingEngine.ts                 |  376 ++++++
 src/types.ts                                |  182 +++
 src/ws/broadcast.ts                         |   62 +
 tsconfig.json                               |   19 +
 43 files changed, 6358 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bc9cc73
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+node_modules/
+dist/
+.env
+*.db
+*.sqlite
+frontend/node_modules/
+frontend/dist/
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+build/
+.next/
+*.bak
diff --git a/boardroom.db-shm b/boardroom.db-shm
new file mode 100644
index 0000000..129f4b4
Binary files /dev/null and b/boardroom.db-shm differ
diff --git a/boardroom.db-wal b/boardroom.db-wal
new file mode 100644
index 0000000..07f7d0f
Binary files /dev/null and b/boardroom.db-wal differ
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
new file mode 100644
index 0000000..3825241
--- /dev/null
+++ b/ecosystem.config.cjs
@@ -0,0 +1,29 @@
+module.exports = {
+  apps: [
+    {
+      name: 'dw-boardroom-api',
+      script: 'dist/api/server.js',
+      cwd: '/root/Projects/dw-boardroom-v2',
+      env: {
+        PORT: 4040,
+        NODE_ENV: 'production',
+        GOVERNANCE_URL: 'http://127.0.0.1:4020',
+        GEMINI_KEY: 'REDACTED_GEMINI_KEY',
+        AUTH_USER: 'admin',
+        AUTH_PASS: 'REDACTED_PASSWORD',
+        FRONTEND_URL: 'http://45.61.58.125:4050',
+      },
+      node_args: '--max-old-space-size=128',
+      max_memory_restart: '150M',
+    },
+    {
+      name: 'dw-boardroom-ui',
+      script: './serve.cjs',
+      cwd: '/root/Projects/dw-boardroom-v2/frontend',
+      env: {
+        NODE_ENV: 'production',
+      },
+      max_memory_restart: '100M',
+    }
+  ]
+};
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..2268be5
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>DW Boardroom V2</title>
+  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
+  <style>
+    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+    body {
+      font-family: 'Inter', -apple-system, system-ui, sans-serif;
+      background: #0a0a12;
+      color: #e8eaf0;
+      min-height: 100vh;
+      -webkit-font-smoothing: antialiased;
+    }
+    #root { min-height: 100vh; }
+    ::-webkit-scrollbar { width: 6px; }
+    ::-webkit-scrollbar-track { background: transparent; }
+    ::-webkit-scrollbar-thumb { background: #252540; border-radius: 3px; }
+    ::-webkit-scrollbar-thumb:hover { background: #8b5cf6; }
+    input[type="range"] {
+      -webkit-appearance: none;
+      height: 4px;
+      background: #252540;
+      border-radius: 2px;
+      outline: none;
+    }
+    input[type="range"]::-webkit-slider-thumb {
+      -webkit-appearance: none;
+      width: 14px; height: 14px;
+      border-radius: 50%;
+      background: #8b5cf6;
+      cursor: pointer;
+    }
+  </style>
+</head>
+<body>
+  <div id="root"></div>
+  <script type="module" src="/src/main.tsx"></script>
+</body>
+</html>
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..a87c0b3
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,1846 @@
+{
+  "name": "dw-boardroom-v2-frontend",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "dw-boardroom-v2-frontend",
+      "version": "1.0.0",
+      "dependencies": {
+        "react": "^18.3.1",
+        "react-dom": "^18.3.1"
+      },
+      "devDependencies": {
+        "@types/react": "^18.3.12",
+        "@types/react-dom": "^18.3.1",
+        "@vitejs/plugin-react": "^4.3.4",
+        "typescript": "^5.7.2",
+        "vite": "^6.0.3"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+      "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+      "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-compilation-targets": "^7.28.6",
+        "@babel/helper-module-transforms": "^7.28.6",
+        "@babel/helpers": "^7.28.6",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/traverse": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/remapping": "^2.3.5",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.29.1",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+      "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+      "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.28.6",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-globals": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+      "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+      "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+      "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.28.6",
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "@babel/traverse": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+      "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+      "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+      "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+      "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.28.6",
+        "@babel/parser": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+      "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-globals": "^7.28.0",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.29.0",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+      "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+      "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+      "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+      "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+      "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+      "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+      "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+      "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+      "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+      "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+      "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+      "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+      "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+      "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+      "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+      "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+      "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+      "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+      "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+      "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+      "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+      "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+      "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.27",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+      "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+      "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+      "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+      "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+      "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+      "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+      "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+      "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+      "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+      "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+      "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+      "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+      "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+      "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+      "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+      "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+      "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+      "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+      "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+      "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+      "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+      "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+      "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.20.7",
+        "@babel/types": "^7.20.7",
+        "@types/babel__generator": "*",
+        "@types/babel__template": "*",
+        "@types/babel__traverse": "*"
+      }
+    },
+    "node_modules/@types/babel__generator": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__traverse": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+      "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.2"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/prop-types": {
+      "version": "15.7.15",
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+      "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/react": {
+      "version": "18.3.28",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+      "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/prop-types": "*",
+        "csstype": "^3.2.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "18.3.7",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+      "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "^18.0.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+      "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.28.0",
+        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+        "@rolldown/pluginutils": "1.0.0-beta.27",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.17.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+      }
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+      "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001770",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
+      "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.286",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+      "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+      "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.12",
+        "@esbuild/android-arm": "0.25.12",
+        "@esbuild/android-arm64": "0.25.12",
+        "@esbuild/android-x64": "0.25.12",
+        "@esbuild/darwin-arm64": "0.25.12",
+        "@esbuild/darwin-x64": "0.25.12",
+        "@esbuild/freebsd-arm64": "0.25.12",
+        "@esbuild/freebsd-x64": "0.25.12",
+        "@esbuild/linux-arm": "0.25.12",
+        "@esbuild/linux-arm64": "0.25.12",
+        "@esbuild/linux-ia32": "0.25.12",
+        "@esbuild/linux-loong64": "0.25.12",
+        "@esbuild/linux-mips64el": "0.25.12",
+        "@esbuild/linux-ppc64": "0.25.12",
+        "@esbuild/linux-riscv64": "0.25.12",
+        "@esbuild/linux-s390x": "0.25.12",
+        "@esbuild/linux-x64": "0.25.12",
+        "@esbuild/netbsd-arm64": "0.25.12",
+        "@esbuild/netbsd-x64": "0.25.12",
+        "@esbuild/openbsd-arm64": "0.25.12",
+        "@esbuild/openbsd-x64": "0.25.12",
+        "@esbuild/openharmony-arm64": "0.25.12",
+        "@esbuild/sunos-x64": "0.25.12",
+        "@esbuild/win32-arm64": "0.25.12",
+        "@esbuild/win32-ia32": "0.25.12",
+        "@esbuild/win32-x64": "0.25.12"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.27",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+      "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/react-refresh": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+      "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+      "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.57.1",
+        "@rollup/rollup-android-arm64": "4.57.1",
+        "@rollup/rollup-darwin-arm64": "4.57.1",
+        "@rollup/rollup-darwin-x64": "4.57.1",
+        "@rollup/rollup-freebsd-arm64": "4.57.1",
+        "@rollup/rollup-freebsd-x64": "4.57.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+        "@rollup/rollup-linux-arm64-musl": "4.57.1",
+        "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+        "@rollup/rollup-linux-loong64-musl": "4.57.1",
+        "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+        "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+        "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-musl": "4.57.1",
+        "@rollup/rollup-openbsd-x64": "4.57.1",
+        "@rollup/rollup-openharmony-arm64": "4.57.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+        "@rollup/rollup-win32-x64-gnu": "4.57.1",
+        "@rollup/rollup-win32-x64-msvc": "4.57.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+      "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2",
+        "postcss": "^8.5.3",
+        "rollup": "^4.34.9",
+        "tinyglobby": "^0.2.13"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true,
+      "license": "ISC"
+    }
+  }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..27fd7b7
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "dw-boardroom-v2-frontend",
+  "private": true,
+  "version": "1.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1"
+  },
+  "devDependencies": {
+    "@types/react": "^18.3.12",
+    "@types/react-dom": "^18.3.1",
+    "@vitejs/plugin-react": "^4.3.4",
+    "typescript": "^5.7.2",
+    "vite": "^6.0.3"
+  }
+}
diff --git a/frontend/serve.cjs b/frontend/serve.cjs
new file mode 100644
index 0000000..55e44b1
--- /dev/null
+++ b/frontend/serve.cjs
@@ -0,0 +1,43 @@
+// Simple static server with Basic Auth for Boardroom V2 UI
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = 4050;
+const USER = 'admin';
+const PASS = 'REDACTED_PASSWORD';
+const DIST = path.join(__dirname, 'dist');
+
+const MIME = {
+  '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css',
+  '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
+  '.svg': 'image/svg+xml', '.woff2': 'font/woff2', '.woff': 'font/woff',
+};
+
+const server = http.createServer((req, res) => {
+  const auth = req.headers.authorization;
+  if (!auth || !auth.startsWith('Basic ')) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Boardroom V2"' });
+    return res.end('Unauthorized');
+  }
+  const [u, p] = Buffer.from(auth.slice(6), 'base64').toString().split(':');
+  if (u !== USER || p !== PASS) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Boardroom V2"' });
+    return res.end('Unauthorized');
+  }
+
+  let filePath = path.join(DIST, req.url === '/' ? 'index.html' : req.url);
+  if (!fs.existsSync(filePath)) filePath = path.join(DIST, 'index.html');
+  const ext = path.extname(filePath);
+  const mime = MIME[ext] || 'application/octet-stream';
+  try {
+    const content = fs.readFileSync(filePath);
+    res.writeHead(200, { 'Content-Type': mime });
+    res.end(content);
+  } catch {
+    res.writeHead(404);
+    res.end('Not found');
+  }
+});
+
+server.listen(PORT, '0.0.0.0', () => console.log(`[Boardroom V2 UI] Serving on port ${PORT} with auth`));
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..d349b1c
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,108 @@
+import { useState, useEffect } from 'react';
+import { api } from './api';
+import { useWebSocket } from './hooks/useWebSocket';
+import MeetingRoom from './components/MeetingRoom';
+import AgendaManager from './components/AgendaManager';
+import AgentRoster from './components/AgentRoster';
+
+const TABS = [
+  { id: 'meeting', label: 'Meeting Room', icon: '\uD83C\uDFDB' },
+  { id: 'agenda', label: 'Agenda', icon: '\uD83D\uDCCB' },
+  { id: 'agents', label: 'Agents', icon: '\uD83E\uDD16' },
+];
+
+export default function App() {
+  const [tab, setTab] = useState('meeting');
+  const [status, setStatus] = useState<any>(null);
+  const { events, connected } = useWebSocket();
+
+  useEffect(() => {
+    api.getStatus().then(setStatus).catch(() => {});
+    const interval = setInterval(() => {
+      api.getStatus().then(setStatus).catch(() => {});
+    }, 15000);
+    return () => clearInterval(interval);
+  }, []);
+
+  return (
+    <div style={{ minHeight: '100vh', background: '#0a0a12' }}>
+      {/* Header */}
+      <header style={styles.header}>
+        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
+          <span style={{ fontSize: 20 }}>{'\uD83C\uDFDB'}</span>
+          <h1 style={{ fontSize: 16, fontWeight: 800, margin: 0, letterSpacing: '-0.3px' }}>DW Boardroom V2</h1>
+        </div>
+        <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
+          <span style={{ ...styles.badge, background: connected ? '#22c55e22' : '#ef444422', color: connected ? '#22c55e' : '#ef4444' }}>
+            {connected ? 'WS Live' : 'WS Off'}
+          </span>
+          {status?.activeMeeting && (
+            <span style={{ ...styles.badge, background: '#8b5cf622', color: '#8b5cf6' }}>
+              {status.activeMeeting.type} — {status.activeMeeting.phase}
+            </span>
+          )}
+          <span style={{ ...styles.badge, background: '#3b82f622', color: '#3b82f6' }}>
+            {status?.agents || 0} agents
+          </span>
+          <span style={{
+            ...styles.badge,
+            background: status?.governance?.available ? '#22c55e22' : '#ef444422',
+            color: status?.governance?.available ? '#22c55e' : '#ef4444'
+          }}>
+            Gov {status?.governance?.available ? 'OK' : 'OFF'}
+          </span>
+        </div>
+      </header>
+
+      {/* Tabs */}
+      <nav style={styles.nav}>
+        {TABS.map(t => (
+          <button
+            key={t.id}
+            onClick={() => setTab(t.id)}
+            style={{
+              ...styles.tab,
+              ...(tab === t.id ? styles.tabActive : {}),
+            }}
+          >
+            <span style={{ fontSize: 14 }}>{t.icon}</span>
+            {t.label}
+          </button>
+        ))}
+      </nav>
+
+      {/* Content */}
+      <main style={styles.main}>
+        {tab === 'meeting' && <MeetingRoom events={events} />}
+        {tab === 'agenda' && <AgendaManager />}
+        {tab === 'agents' && <AgentRoster />}
+      </main>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  header: {
+    display: 'flex', justifyContent: 'space-between', alignItems: 'center',
+    padding: '12px 24px', borderBottom: '1px solid #252540',
+    background: 'linear-gradient(180deg, #0f0f1e 0%, #0a0a12 100%)',
+  },
+  badge: {
+    padding: '4px 10px', borderRadius: 6, fontSize: 11, fontWeight: 700,
+  },
+  nav: {
+    display: 'flex', gap: 4, padding: '8px 24px', borderBottom: '1px solid #252540',
+  },
+  tab: {
+    display: 'flex', alignItems: 'center', gap: 6,
+    padding: '8px 16px', borderRadius: 8, border: 'none',
+    background: 'transparent', color: '#7a7f96', fontWeight: 600, fontSize: 12,
+    cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.2s',
+  },
+  tabActive: {
+    background: '#8b5cf622', color: '#8b5cf6',
+  },
+  main: {
+    padding: 24, maxWidth: 1440, margin: '0 auto',
+  },
+};
diff --git a/frontend/src/api.ts b/frontend/src/api.ts
new file mode 100644
index 0000000..a528de5
--- /dev/null
+++ b/frontend/src/api.ts
@@ -0,0 +1,47 @@
+const BASE = 'http://45.61.58.125:4040';
+const AUTH = btoa('admin:REDACTED_PASSWORD');
+const headers: Record<string, string> = {
+  'Content-Type': 'application/json',
+  'Authorization': `Basic ${AUTH}`,
+};
+
+async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
+  const res = await fetch(`${BASE}${path}`, { headers, ...options });
+  if (!res.ok) {
+    const text = await res.text();
+    throw new Error(`${res.status}: ${text}`);
+  }
+  return res.json();
+}
+
+export const api = {
+  // Meetings
+  getMeetings: () => apiFetch<any[]>('/api/meetings'),
+  getActiveMeeting: () => apiFetch<any>('/api/meetings/active'),
+  getMeeting: (id: string) => apiFetch<any>(`/api/meetings/${id}`),
+  startMeeting: (type: string) => apiFetch<any>(`/api/meetings/start/${type}`, { method: 'POST' }),
+  haltMeeting: (id: string) => apiFetch<any>(`/api/meetings/${id}/halt`, { method: 'POST' }),
+  advanceMeeting: (id: string) => apiFetch<any>(`/api/meetings/${id}/advance`, { method: 'POST' }),
+  addMessage: (id: string, msg: any) => apiFetch<any>(`/api/meetings/${id}/message`, { method: 'POST', body: JSON.stringify(msg) }),
+  getMessages: (id: string) => apiFetch<any[]>(`/api/meetings/${id}/messages`),
+
+  // Agents
+  getAgents: () => apiFetch<any>('/api/agents'),
+  getTeams: () => apiFetch<any>('/api/agents/teams'),
+  getAgent: (name: string) => apiFetch<any>(`/api/agents/${name}`),
+  speakAgent: (name: string, topic: string, phase?: string) =>
+    apiFetch<any>(`/api/agents/${name}/speak`, { method: 'POST', body: JSON.stringify({ topic, phase }) }),
+
+  // Agenda
+  getAgenda: (meetingId: string) => apiFetch<any[]>(`/api/agenda/${meetingId}`),
+  addAgendaItem: (meetingId: string, item: any) => apiFetch<any>(`/api/agenda/${meetingId}`, { method: 'POST', body: JSON.stringify(item) }),
+  updateAgendaItem: (meetingId: string, itemId: number, updates: any) => apiFetch<any>(`/api/agenda/${meetingId}/${itemId}`, { method: 'PUT', body: JSON.stringify(updates) }),
+  deleteAgendaItem: (meetingId: string, itemId: number) => apiFetch<any>(`/api/agenda/${meetingId}/${itemId}`, { method: 'DELETE' }),
+
+  // Voice
+  getVoiceConfig: () => apiFetch<any>('/api/voice/config'),
+  updateVoiceConfig: (cfg: any) => apiFetch<any>('/api/voice/config', { method: 'POST', body: JSON.stringify(cfg) }),
+
+  // Status
+  getStatus: () => apiFetch<any>('/api/status'),
+};
diff --git a/frontend/src/components/AgendaManager.tsx b/frontend/src/components/AgendaManager.tsx
new file mode 100644
index 0000000..22c9000
--- /dev/null
+++ b/frontend/src/components/AgendaManager.tsx
@@ -0,0 +1,132 @@
+import { useState, useEffect } from 'react';
+import { api } from '../api';
+import type { AgendaItem, Meeting } from '../types';
+
+export default function AgendaManager() {
+  const [meeting, setMeeting] = useState<Meeting | null>(null);
+  const [items, setItems] = useState<AgendaItem[]>([]);
+  const [topic, setTopic] = useState('');
+  const [recommendation, setRecommendation] = useState('');
+  const [decisionType, setDecisionType] = useState('B');
+  const [owner, setOwner] = useState('');
+
+  useEffect(() => {
+    api.getActiveMeeting().then(data => {
+      setMeeting(data.meeting);
+      if (data.meeting) {
+        api.getAgenda(data.meeting.id).then(setItems);
+      }
+    }).catch(() => {});
+  }, []);
+
+  function refresh() {
+    if (meeting) {
+      api.getAgenda(meeting.id).then(setItems);
+    }
+  }
+
+  async function addItem() {
+    if (!meeting || !topic.trim()) return;
+    try {
+      await api.addAgendaItem(meeting.id, { topic, recommendation, decisionType, owner: owner || undefined });
+      setTopic(''); setRecommendation(''); setOwner('');
+      refresh();
+    } catch (err: any) {
+      alert(err.message);
+    }
+  }
+
+  async function deleteItem(id: number) {
+    if (!meeting) return;
+    await api.deleteAgendaItem(meeting.id, id);
+    refresh();
+  }
+
+  if (!meeting) {
+    return (
+      <div style={styles.card}>
+        <div style={styles.empty}>No active meeting. Start a meeting first to manage its agenda.</div>
+      </div>
+    );
+  }
+
+  return (
+    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
+      {/* Add Item Form */}
+      <div style={styles.card}>
+        <h3 style={styles.title}>Add Agenda Item</h3>
+        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
+          <input style={styles.input} placeholder="Topic" value={topic} onChange={e => setTopic(e.target.value)} />
+          <input style={styles.input} placeholder="Recommendation" value={recommendation} onChange={e => setRecommendation(e.target.value)} />
+          <select style={styles.select} value={decisionType} onChange={e => setDecisionType(e.target.value)}>
+            <option value="A">Type A (Auto)</option>
+            <option value="B">Type B (Advisory)</option>
+            <option value="C">Type C (Human)</option>
+          </select>
+          <input style={{ ...styles.input, maxWidth: 120 }} placeholder="Owner" value={owner} onChange={e => setOwner(e.target.value)} />
+          <button style={styles.addBtn} onClick={addItem} disabled={items.length >= 5}>
+            {items.length >= 5 ? 'Max 5' : 'Add'}
+          </button>
+        </div>
+        <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 6 }}>
+          {items.length}/5 items | Meeting: {meeting.meeting_type} ({meeting.id.slice(0, 8)})
+        </div>
+      </div>
+
+      {/* Items List */}
+      <div style={styles.card}>
+        <h3 style={styles.title}>Current Agenda</h3>
+        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
+          {items.length === 0 && <div style={styles.empty}>No agenda items yet.</div>}
+          {items.map((item, idx) => (
+            <div key={item.id} style={styles.item}>
+              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+                  <span style={styles.position}>{idx + 1}</span>
+                  <span style={{ fontWeight: 600, fontSize: 13 }}>{item.topic}</span>
+                </div>
+                <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
+                  <span style={{
+                    ...styles.typeBadge,
+                    background: item.decision_type === 'A' ? '#22c55e22' : item.decision_type === 'C' ? '#ef444422' : '#3b82f622',
+                    color: item.decision_type === 'A' ? '#22c55e' : item.decision_type === 'C' ? '#ef4444' : '#3b82f6',
+                  }}>
+                    Type {item.decision_type}
+                  </span>
+                  <span style={{
+                    ...styles.statusBadge,
+                    background: item.status === 'discussed' ? '#f59e0b22' : item.status === 'resolved' ? '#22c55e22' : '#7a7f9622',
+                    color: item.status === 'discussed' ? '#f59e0b' : item.status === 'resolved' ? '#22c55e' : '#7a7f96',
+                  }}>
+                    {item.status}
+                  </span>
+                  <button style={styles.deleteBtn} onClick={() => deleteItem(item.id)}>x</button>
+                </div>
+              </div>
+              {item.recommendation && (
+                <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 4 }}>Rec: {item.recommendation}</div>
+              )}
+              {item.owner && (
+                <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 2 }}>Owner: {item.owner}</div>
+              )}
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  title: { margin: '0 0 12px', fontSize: 15, fontWeight: 700 },
+  input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1, minWidth: 140 },
+  select: { padding: '8px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  addBtn: { padding: '8px 16px', borderRadius: 6, border: 'none', background: '#8b5cf6', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 30 },
+  item: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #252540' },
+  position: { width: 22, height: 22, borderRadius: '50%', background: '#8b5cf622', color: '#8b5cf6', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700 },
+  typeBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+  deleteBtn: { width: 20, height: 20, borderRadius: 4, border: '1px solid #ef444466', background: 'transparent', color: '#ef4444', fontSize: 11, cursor: 'pointer', fontFamily: 'inherit' },
+};
diff --git a/frontend/src/components/AgentRoster.tsx b/frontend/src/components/AgentRoster.tsx
new file mode 100644
index 0000000..094deca
--- /dev/null
+++ b/frontend/src/components/AgentRoster.tsx
@@ -0,0 +1,125 @@
+import { useState } from 'react';
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+import type { Agent, Executive } from '../types';
+
+export default function AgentRoster() {
+  const { data, loading } = useApi(() => api.getAgents(), []);
+  const [search, setSearch] = useState('');
+  const [speakingAgent, setSpeakingAgent] = useState<string | null>(null);
+  const [dialogue, setDialogue] = useState<string | null>(null);
+
+  if (loading || !data) return <div style={styles.card}><div style={styles.empty}>Loading agents...</div></div>;
+
+  const agents: Agent[] = data.agents || [];
+  const executives: Executive[] = data.executives || [];
+  const departments = [...new Set(agents.map(a => a.dept))].sort();
+
+  const filtered = search
+    ? agents.filter(a => a.name.toLowerCase().includes(search.toLowerCase()) || a.role.toLowerCase().includes(search.toLowerCase()) || a.id.toLowerCase().includes(search.toLowerCase()))
+    : agents;
+
+  async function speak(agentId: string) {
+    setSpeakingAgent(agentId);
+    setDialogue(null);
+    try {
+      const res = await api.speakAgent(agentId, 'Share your current status and priorities');
+      setDialogue(res.dialogue);
+    } catch {
+      setDialogue('Unable to generate dialogue.');
+    }
+  }
+
+  return (
+    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
+      {/* Search + Stats */}
+      <div style={styles.card}>
+        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
+          <h3 style={{ margin: 0, fontSize: 16, fontWeight: 700 }}>Agent Directory</h3>
+          <span style={{ fontSize: 12, color: '#7a7f96' }}>{agents.length} agents + {executives.length} executives</span>
+        </div>
+        <input
+          style={styles.search}
+          placeholder="Search agents by name, role, or ID..."
+          value={search}
+          onChange={e => setSearch(e.target.value)}
+        />
+      </div>
+
+      {/* Dialogue preview */}
+      {dialogue && (
+        <div style={{ ...styles.card, borderColor: '#8b5cf666' }}>
+          <div style={{ fontSize: 12, fontWeight: 700, color: '#8b5cf6', marginBottom: 6 }}>
+            {speakingAgent} says:
+          </div>
+          <div style={{ fontSize: 13, lineHeight: 1.5 }}>{dialogue}</div>
+        </div>
+      )}
+
+      {/* Executives */}
+      <div style={styles.card}>
+        <h4 style={{ margin: '0 0 12px', fontSize: 13, fontWeight: 700, color: '#8b5cf6' }}>Executive Team</h4>
+        <div style={styles.grid}>
+          {executives.map(exec => (
+            <div key={exec.id} style={{ ...styles.agentCard, borderColor: exec.color + '44' }}>
+              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+                <div style={{ ...styles.dot, background: exec.color }} />
+                <div>
+                  <div style={{ fontWeight: 700, fontSize: 13 }}>{exec.name}</div>
+                  <div style={{ fontSize: 10, color: '#7a7f96' }}>{exec.role}</div>
+                </div>
+              </div>
+              <div style={{ fontSize: 10, color: '#7a7f96', marginTop: 6 }}>
+                ID: {exec.id} | Port: {exec.port}
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+
+      {/* Agents by Department */}
+      {departments.map(dept => {
+        const deptAgents = filtered.filter(a => a.dept === dept);
+        if (deptAgents.length === 0) return null;
+        return (
+          <div key={dept} style={styles.card}>
+            <h4 style={{ margin: '0 0 10px', fontSize: 13, fontWeight: 700, color: '#f59e0b' }}>{dept}</h4>
+            <div style={styles.grid}>
+              {deptAgents.map(agent => (
+                <div
+                  key={agent.id}
+                  style={{
+                    ...styles.agentCard,
+                    borderColor: agent.color + '44',
+                    cursor: 'pointer',
+                  }}
+                  onClick={() => speak(agent.id)}
+                >
+                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+                    <div style={{ ...styles.dot, background: agent.color }} />
+                    <div>
+                      <div style={{ fontWeight: 600, fontSize: 12 }}>{agent.name}</div>
+                      <div style={{ fontSize: 10, color: '#7a7f96' }}>{agent.role}</div>
+                    </div>
+                  </div>
+                  <div style={{ fontSize: 9, color: '#555', marginTop: 4 }}>
+                    {agent.id} {agent.port ? `| :${agent.port}` : ''} {agent.hasApi ? '| API' : ''}
+                  </div>
+                </div>
+              ))}
+            </div>
+          </div>
+        );
+      })}
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40 },
+  search: { width: '100%', padding: '10px 12px', borderRadius: 8, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 13, fontFamily: 'inherit' },
+  grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 8 },
+  agentCard: { background: '#12121f', borderRadius: 8, padding: 10, border: '1px solid #252540', transition: 'all 0.2s' },
+  dot: { width: 10, height: 10, borderRadius: '50%', flexShrink: 0 },
+};
diff --git a/frontend/src/components/LiveFeed.tsx b/frontend/src/components/LiveFeed.tsx
new file mode 100644
index 0000000..8b93010
--- /dev/null
+++ b/frontend/src/components/LiveFeed.tsx
@@ -0,0 +1,77 @@
+import { useEffect, useRef } from 'react';
+import type { MeetingMessage } from '../types';
+
+const TYPE_COLORS: Record<string, string> = {
+  dialogue: '#e8eaf0',
+  phase: '#8b5cf6',
+  system: '#7a7f96',
+  action: '#f59e0b',
+  decision: '#22c55e',
+};
+
+interface Props {
+  messages: MeetingMessage[];
+  meetingActive: boolean;
+}
+
+export default function LiveFeed({ messages, meetingActive }: Props) {
+  const feedRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    if (feedRef.current) {
+      feedRef.current.scrollTop = feedRef.current.scrollHeight;
+    }
+  }, [messages]);
+
+  return (
+    <div style={styles.card}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
+        <h3 style={{ margin: 0, fontSize: 14, fontWeight: 700 }}>Live Feed</h3>
+        <span style={{ fontSize: 11, color: '#7a7f96' }}>
+          {messages.length} message{messages.length !== 1 ? 's' : ''}
+        </span>
+      </div>
+
+      <div ref={feedRef} style={styles.feed}>
+        {messages.length === 0 && (
+          <div style={styles.empty}>
+            {meetingActive ? 'Waiting for dialogue...' : 'No active meeting. Start one above.'}
+          </div>
+        )}
+        {messages.map((msg) => (
+          <div key={msg.id} style={styles.message}>
+            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}>
+              <span style={{ fontWeight: 700, fontSize: 12, color: TYPE_COLORS[msg.message_type] || '#e8eaf0' }}>
+                {msg.agent_name}
+              </span>
+              {msg.message_type !== 'dialogue' && (
+                <span style={{
+                  padding: '1px 6px', borderRadius: 3, fontSize: 9, fontWeight: 600,
+                  background: (TYPE_COLORS[msg.message_type] || '#7a7f96') + '22',
+                  color: TYPE_COLORS[msg.message_type] || '#7a7f96',
+                }}>
+                  {msg.message_type}
+                </span>
+              )}
+              <span style={{ fontSize: 9, color: '#555', marginLeft: 'auto' }}>
+                {msg.phase}
+              </span>
+            </div>
+            <div style={{ fontSize: 12, color: '#b0b3c0', lineHeight: 1.5 }}>{msg.message}</div>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 16, border: '1px solid #252540' },
+  feed: {
+    maxHeight: 500, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 8,
+  },
+  message: {
+    padding: '8px 12px', background: '#12121f', borderRadius: 8, borderLeft: '3px solid #252540',
+  },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40 },
+};
diff --git a/frontend/src/components/MeetingControls.tsx b/frontend/src/components/MeetingControls.tsx
new file mode 100644
index 0000000..1a04a0a
--- /dev/null
+++ b/frontend/src/components/MeetingControls.tsx
@@ -0,0 +1,74 @@
+import type { Meeting } from '../types';
+
+const MEETING_TYPES = [
+  { type: 'strategic', label: 'Strategic', color: '#8b5cf6', icon: '\uD83C\uDFAF' },
+  { type: 'initiative', label: 'Initiative', color: '#3b82f6', icon: '\uD83D\uDE80' },
+  { type: 'ops', label: 'Operations', color: '#22c55e', icon: '\u2699\uFE0F' },
+  { type: 'emergency', label: 'Emergency', color: '#ef4444', icon: '\uD83D\uDEA8' },
+];
+
+interface Props {
+  activeMeeting: Meeting | null;
+  onStart: (type: string) => void;
+  onHalt: () => void;
+  onAdvance: () => void;
+}
+
+export default function MeetingControls({ activeMeeting, onStart, onHalt, onAdvance }: Props) {
+  const isActive = activeMeeting && activeMeeting.status === 'in_progress';
+
+  return (
+    <div style={styles.card}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+        <h3 style={{ margin: 0, fontSize: 14, fontWeight: 700 }}>Meeting Controls</h3>
+        {isActive && (
+          <div style={{ display: 'flex', gap: 6 }}>
+            <button style={styles.advanceBtn} onClick={onAdvance}>Advance Phase</button>
+            <button style={styles.haltBtn} onClick={onHalt}>Halt</button>
+          </div>
+        )}
+      </div>
+
+      {!isActive && (
+        <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
+          {MEETING_TYPES.map(mt => (
+            <button
+              key={mt.type}
+              style={{ ...styles.startBtn, borderColor: mt.color + '66', color: mt.color }}
+              onClick={() => onStart(mt.type)}
+            >
+              <span>{mt.icon}</span>
+              <span>{mt.label}</span>
+            </button>
+          ))}
+        </div>
+      )}
+
+      {isActive && (
+        <div style={{ marginTop: 8, fontSize: 11, color: '#7a7f96' }}>
+          {activeMeeting.meeting_type} meeting in progress — Phase: {activeMeeting.phase.replace(/_/g, ' ')}
+        </div>
+      )}
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 16, border: '1px solid #252540' },
+  startBtn: {
+    flex: 1, padding: '10px 12px', borderRadius: 8, border: '1px solid',
+    background: 'transparent', fontWeight: 600, fontSize: 12,
+    cursor: 'pointer', fontFamily: 'inherit', display: 'flex', alignItems: 'center',
+    justifyContent: 'center', gap: 6, transition: 'all 0.2s',
+  },
+  advanceBtn: {
+    padding: '6px 14px', borderRadius: 6, border: 'none',
+    background: '#8b5cf6', color: '#fff', fontWeight: 600, fontSize: 11,
+    cursor: 'pointer', fontFamily: 'inherit',
+  },
+  haltBtn: {
+    padding: '6px 14px', borderRadius: 6, border: 'none',
+    background: '#ef4444', color: '#fff', fontWeight: 600, fontSize: 11,
+    cursor: 'pointer', fontFamily: 'inherit',
+  },
+};
diff --git a/frontend/src/components/MeetingRoom.tsx b/frontend/src/components/MeetingRoom.tsx
new file mode 100644
index 0000000..f5fea96
--- /dev/null
+++ b/frontend/src/components/MeetingRoom.tsx
@@ -0,0 +1,86 @@
+import { useState, useEffect, useRef } from 'react';
+import { api } from '../api';
+import type { Meeting, MeetingMessage, WSEvent } from '../types';
+import PhaseBar from './PhaseBar';
+import MeetingControls from './MeetingControls';
+import LiveFeed from './LiveFeed';
+
+interface Props {
+  events: WSEvent[];
+}
+
+export default function MeetingRoom({ events }: Props) {
+  const [meeting, setMeeting] = useState<Meeting | null>(null);
+  const [messages, setMessages] = useState<MeetingMessage[]>([]);
+  const [loading, setLoading] = useState(true);
+
+  function fetchActive() {
+    api.getActiveMeeting().then(data => {
+      setMeeting(data.meeting);
+      setMessages(data.messages || []);
+      setLoading(false);
+    }).catch(() => setLoading(false));
+  }
+
+  useEffect(() => { fetchActive(); }, []);
+
+  // Listen for WS events
+  useEffect(() => {
+    const latest = events[events.length - 1];
+    if (!latest) return;
+
+    if (latest.type === 'meeting_started' || latest.type === 'meeting_completed' || latest.type === 'meeting_halted') {
+      fetchActive();
+    } else if (latest.type === 'meeting_phase' && meeting) {
+      setMeeting(prev => prev ? { ...prev, phase: latest.payload.phase } : null);
+    } else if (latest.type === 'meeting_message') {
+      setMessages(prev => [...prev, latest.payload]);
+    }
+  }, [events]);
+
+  async function handleStart(type: string) {
+    try {
+      await api.startMeeting(type);
+      fetchActive();
+    } catch (err: any) {
+      alert(err.message);
+    }
+  }
+
+  async function handleHalt() {
+    if (!meeting) return;
+    await api.haltMeeting(meeting.id);
+    fetchActive();
+  }
+
+  async function handleAdvance() {
+    if (!meeting) return;
+    await api.advanceMeeting(meeting.id);
+    fetchActive();
+  }
+
+  if (loading) return <div style={styles.card}><div style={styles.empty}>Loading...</div></div>;
+
+  return (
+    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
+      {/* Phase Bar */}
+      <PhaseBar currentPhase={meeting?.phase || null} status={meeting?.status || null} />
+
+      {/* Controls */}
+      <MeetingControls
+        activeMeeting={meeting}
+        onStart={handleStart}
+        onHalt={handleHalt}
+        onAdvance={handleAdvance}
+      />
+
+      {/* Live Feed */}
+      <LiveFeed messages={messages} meetingActive={!!meeting && meeting.status === 'in_progress'} />
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40 },
+};
diff --git a/frontend/src/components/PhaseBar.tsx b/frontend/src/components/PhaseBar.tsx
new file mode 100644
index 0000000..a933b51
--- /dev/null
+++ b/frontend/src/components/PhaseBar.tsx
@@ -0,0 +1,68 @@
+const PHASES = [
+  { id: 'caucus', label: 'Caucus' },
+  { id: 'call_to_order', label: 'Call to Order' },
+  { id: 'old_business', label: 'Old Business' },
+  { id: 'current_business', label: 'Current Business' },
+  { id: 'new_business', label: 'New Business' },
+  { id: 'breakout', label: 'Breakout' },
+  { id: 'minutes', label: 'Minutes' },
+  { id: 'adjournment', label: 'Adjournment' },
+];
+
+interface Props {
+  currentPhase: string | null;
+  status: string | null;
+}
+
+export default function PhaseBar({ currentPhase, status }: Props) {
+  const activeIdx = PHASES.findIndex(p => p.id === currentPhase);
+
+  return (
+    <div style={styles.bar}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
+        <span style={{ fontSize: 12, fontWeight: 700 }}>Meeting Phases</span>
+        {status && (
+          <span style={{
+            ...styles.statusBadge,
+            background: status === 'in_progress' ? '#22c55e22' : status === 'halted' ? '#ef444422' : '#7a7f9622',
+            color: status === 'in_progress' ? '#22c55e' : status === 'halted' ? '#ef4444' : '#7a7f96',
+          }}>
+            {status.replace('_', ' ')}
+          </span>
+        )}
+      </div>
+      <div style={styles.phases}>
+        {PHASES.map((p, i) => {
+          const isActive = i === activeIdx;
+          const isPast = i < activeIdx;
+          return (
+            <div
+              key={p.id}
+              style={{
+                ...styles.phase,
+                background: isActive ? '#8b5cf6' : isPast ? '#8b5cf644' : '#252540',
+                color: isActive ? '#fff' : isPast ? '#b0b3c0' : '#7a7f96',
+                fontWeight: isActive ? 700 : 400,
+                boxShadow: isActive ? '0 0 12px #8b5cf644' : 'none',
+              }}
+            >
+              <span style={{ fontSize: 8, opacity: 0.6 }}>{i + 1}</span>
+              <span style={{ fontSize: 10 }}>{p.label}</span>
+            </div>
+          );
+        })}
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  bar: { background: '#1a1a2e', borderRadius: 12, padding: 16, border: '1px solid #252540' },
+  phases: { display: 'flex', gap: 4 },
+  phase: {
+    flex: 1, padding: '8px 4px', borderRadius: 6, textAlign: 'center' as const,
+    display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
+    transition: 'all 0.3s',
+  },
+  statusBadge: { padding: '3px 10px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+};
diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts
new file mode 100644
index 0000000..14b5193
--- /dev/null
+++ b/frontend/src/hooks/useApi.ts
@@ -0,0 +1,20 @@
+import { useState, useEffect, useCallback } from 'react';
+
+export function useApi<T>(fetcher: () => Promise<T>, deps: any[] = []) {
+  const [data, setData] = useState<T | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const refresh = useCallback(() => {
+    setLoading(true);
+    setError(null);
+    fetcher()
+      .then(setData)
+      .catch(e => setError(e.message))
+      .finally(() => setLoading(false));
+  }, deps);
+
+  useEffect(() => { refresh(); }, [refresh]);
+
+  return { data, loading, error, refresh };
+}
diff --git a/frontend/src/hooks/useWebSocket.ts b/frontend/src/hooks/useWebSocket.ts
new file mode 100644
index 0000000..c293c80
--- /dev/null
+++ b/frontend/src/hooks/useWebSocket.ts
@@ -0,0 +1,54 @@
+import { useState, useEffect, useRef, useCallback } from 'react';
+import type { WSEvent } from '../types';
+
+const WS_URL = `ws://${window.location.host}/ws`;
+const MAX_EVENTS = 200;
+
+export function useWebSocket() {
+  const [events, setEvents] = useState<WSEvent[]>([]);
+  const [connected, setConnected] = useState(false);
+  const wsRef = useRef<WebSocket | null>(null);
+  const reconnectTimer = useRef<number>();
+
+  const connect = useCallback(() => {
+    if (wsRef.current?.readyState === WebSocket.OPEN) return;
+
+    try {
+      const ws = new WebSocket(WS_URL);
+      wsRef.current = ws;
+
+      ws.onopen = () => {
+        setConnected(true);
+        if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
+      };
+
+      ws.onmessage = (evt) => {
+        try {
+          const event: WSEvent = JSON.parse(evt.data);
+          setEvents(prev => [...prev.slice(-MAX_EVENTS), event]);
+        } catch {}
+      };
+
+      ws.onclose = () => {
+        setConnected(false);
+        reconnectTimer.current = window.setTimeout(connect, 3000);
+      };
+
+      ws.onerror = () => {
+        ws.close();
+      };
+    } catch {}
+  }, []);
+
+  useEffect(() => {
+    connect();
+    return () => {
+      if (wsRef.current) wsRef.current.close();
+      if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
+    };
+  }, [connect]);
+
+  const clearEvents = useCallback(() => setEvents([]), []);
+
+  return { events, connected, clearEvents };
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..9707d82
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
new file mode 100644
index 0000000..39e6766
--- /dev/null
+++ b/frontend/src/types.ts
@@ -0,0 +1,62 @@
+export interface Meeting {
+  id: string;
+  meeting_type: string;
+  phase: string;
+  status: string;
+  started_at: string | null;
+  ended_at: string | null;
+  summary: string | null;
+  attendees: string;
+  action_items: string;
+  created_at: string;
+}
+
+export interface MeetingMessage {
+  id: number;
+  meeting_id: string;
+  agent_id: string;
+  agent_name: string;
+  message: string;
+  message_type: string;
+  phase: string | null;
+  created_at: string;
+}
+
+export interface AgendaItem {
+  id: number;
+  meeting_id: string;
+  topic: string;
+  recommendation: string | null;
+  decision_type: string;
+  owner: string | null;
+  position: number;
+  status: string;
+  created_at: string;
+}
+
+export interface Agent {
+  id: string;
+  name: string;
+  pm2: string;
+  port?: number;
+  color: string;
+  role: string;
+  dept: string;
+  hasApi: boolean;
+  subMgr?: string;
+}
+
+export interface Executive {
+  id: string;
+  name: string;
+  port: number;
+  color: string;
+  role: string;
+  avatar: string;
+}
+
+export interface WSEvent {
+  type: string;
+  payload: any;
+  timestamp: string;
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..75e2ff7
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,21 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "strict": true,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noFallthroughCasesInSwitch": true,
+    "forceConsistentCasingInFileNames": true
+  },
+  "include": ["src"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..0abaae4
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+  plugins: [react()],
+  server: {
+    port: 4050,
+    host: '0.0.0.0',
+    proxy: {
+      '/api': 'http://127.0.0.1:4040',
+      '/ws': { target: 'ws://127.0.0.1:4040', ws: true },
+    },
+  },
+  preview: {
+    port: 4050,
+    host: '0.0.0.0',
+    proxy: {
+      '/api': 'http://127.0.0.1:4040',
+      '/ws': { target: 'ws://127.0.0.1:4040', ws: true },
+    },
+  },
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c62abfe
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1626 @@
+{
+  "name": "dw-boardroom-v2",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "dw-boardroom-v2",
+      "version": "1.0.0",
+      "dependencies": {
+        "better-sqlite3": "^11.7.0",
+        "cors": "^2.8.5",
+        "dotenv": "^16.4.7",
+        "express": "^4.21.1",
+        "node-fetch": "^2.7.0",
+        "uuid": "^11.0.3",
+        "ws": "^8.18.0"
+      },
+      "devDependencies": {
+        "@types/better-sqlite3": "^7.6.12",
+        "@types/cors": "^2.8.17",
+        "@types/express": "^5.0.0",
+        "@types/node": "^22.10.1",
+        "@types/node-fetch": "^2.6.12",
+        "@types/uuid": "^10.0.0",
+        "@types/ws": "^8.5.13",
+        "typescript": "^5.7.2"
+      }
+    },
+    "node_modules/@types/better-sqlite3": {
+      "version": "7.6.13",
+      "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+      "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.6",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+      "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.19",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+      "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "5.0.6",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+      "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^5.0.0",
+        "@types/serve-static": "^2"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+      "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+      "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "22.19.11",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
+      "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/node-fetch": {
+      "version": "2.6.13",
+      "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
+      "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "form-data": "^4.0.4"
+      }
+    },
+    "node_modules/@types/qs": {
+      "version": "6.14.0",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/send": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+      "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+      "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/ws": {
+      "version": "8.18.1",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/better-sqlite3": {
+      "version": "11.10.0",
+      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
+      "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "bindings": "^1.5.0",
+        "prebuild-install": "^7.1.1"
+      }
+    },
+    "node_modules/bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "node_modules/bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.4",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+      "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "~1.2.0",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "on-finished": "~2.4.1",
+        "qs": "~6.14.0",
+        "raw-body": "~2.5.3",
+        "type-is": "~1.6.18",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "license": "ISC"
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+      "license": "MIT"
+    },
+    "node_modules/cors": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+      "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/decompress-response": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-response": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.6.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+      "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/end-of-stream": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+      "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+      "license": "MIT",
+      "dependencies": {
+        "once": "^1.4.0"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/expand-template": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+      "license": "(MIT OR WTFPL)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+      "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "~1.20.3",
+        "content-disposition": "~0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "~0.7.1",
+        "cookie-signature": "~1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.3.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "~0.1.12",
+        "proxy-addr": "~2.0.7",
+        "qs": "~6.14.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "~0.19.0",
+        "serve-static": "~1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "license": "MIT"
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "~2.0.2",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fs-constants": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+      "license": "MIT"
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/github-from-package": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+      "license": "MIT"
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ini": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-response": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp-classic": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+      "license": "MIT"
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/napi-build-utils": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+      "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-abi": {
+      "version": "3.87.0",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
+      "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.3.5"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+      "license": "MIT"
+    },
+    "node_modules/prebuild-install": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+      "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+      "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
+      "license": "MIT",
+      "dependencies": {
+        "detect-libc": "^2.0.0",
+        "expand-template": "^2.0.3",
+        "github-from-package": "0.0.0",
+        "minimist": "^1.2.3",
+        "mkdirp-classic": "^0.5.3",
+        "napi-build-utils": "^2.0.0",
+        "node-abi": "^3.3.0",
+        "pump": "^3.0.0",
+        "rc": "^1.2.7",
+        "simple-get": "^4.0.0",
+        "tar-fs": "^2.0.0",
+        "tunnel-agent": "^0.6.0"
+      },
+      "bin": {
+        "prebuild-install": "bin.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/pump": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+      "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+      "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/send": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "~2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "~0.19.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/simple-concat": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/simple-get": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decompress-response": "^6.0.0",
+        "once": "^1.3.1",
+        "simple-concat": "^1.0.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tar-fs": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+      "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
+    "node_modules/tar-stream": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^4.0.3",
+        "end-of-stream": "^1.4.1",
+        "fs-constants": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT"
+    },
+    "node_modules/tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/uuid": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+      "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/esm/bin/uuid"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "8.19.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+      "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9638a8e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+  "name": "dw-boardroom-v2",
+  "version": "1.0.0",
+  "description": "DW Boardroom V2 — Meeting orchestration engine with 47 AI agents",
+  "main": "dist/api/server.js",
+  "scripts": {
+    "build": "tsc",
+    "start": "node dist/api/server.js",
+    "dev": "ts-node src/api/server.ts",
+    "migrate": "ts-node src/db/migrate.ts"
+  },
+  "dependencies": {
+    "better-sqlite3": "^11.7.0",
+    "cors": "^2.8.5",
+    "dotenv": "^16.4.7",
+    "express": "^4.21.1",
+    "node-fetch": "^2.7.0",
+    "uuid": "^11.0.3",
+    "ws": "^8.18.0"
+  },
+  "devDependencies": {
+    "@types/better-sqlite3": "^7.6.12",
+    "@types/cors": "^2.8.17",
+    "@types/express": "^5.0.0",
+    "@types/node": "^22.10.1",
+    "@types/node-fetch": "^2.6.12",
+    "@types/uuid": "^10.0.0",
+    "@types/ws": "^8.5.13",
+    "typescript": "^5.7.2"
+  }
+}
diff --git a/src/agents/president.ts b/src/agents/president.ts
new file mode 100644
index 0000000..4f95259
--- /dev/null
+++ b/src/agents/president.ts
@@ -0,0 +1,58 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — President / Chair Agent
+// Validates agenda, enforces rules, opens/closes
+// ═══════════════════════════════════════════════
+
+import type { AgendaItem, MeetingType } from '../types';
+
+const MAX_AGENDA_ITEMS = 5;
+
+export function validateAgenda(items: Partial<AgendaItem>[]): { valid: boolean; errors: string[] } {
+  const errors: string[] = [];
+
+  if (items.length > MAX_AGENDA_ITEMS) {
+    errors.push(`Maximum ${MAX_AGENDA_ITEMS} agenda items allowed (got ${items.length})`);
+  }
+
+  for (let i = 0; i < items.length; i++) {
+    const item = items[i];
+    if (!item.topic || item.topic.trim().length === 0) {
+      errors.push(`Item ${i + 1}: Topic is required`);
+    }
+    if (item.decision_type && !['A', 'B', 'C'].includes(item.decision_type)) {
+      errors.push(`Item ${i + 1}: Invalid decision type (must be A, B, or C)`);
+    }
+  }
+
+  return { valid: errors.length === 0, errors };
+}
+
+export function generateOpeningStatement(meetingType: MeetingType, agendaCount: number): string {
+  const typeLabels: Record<MeetingType, string> = {
+    strategic: 'Strategic Planning Session',
+    initiative: 'Initiative Review',
+    ops: 'Operations Sync',
+    emergency: 'Emergency Session',
+  };
+
+  const label = typeLabels[meetingType] || 'General Meeting';
+  return `This ${label} is now called to order by President Lincoln. We have ${agendaCount} agenda item${agendaCount !== 1 ? 's' : ''} to address. Let us proceed with focus and efficiency.`;
+}
+
+export function generateClosingStatement(meetingType: MeetingType, decisionsCount: number): string {
+  return `This ${meetingType} session stands adjourned by order of President Lincoln. ${decisionsCount} decision${decisionsCount !== 1 ? 's' : ''} were addressed. Action items will be tracked through governance. You are all dismissed.`;
+}
+
+export function getPhaseChairMessage(phase: string): string {
+  const messages: Record<string, string> = {
+    caucus: 'Members, prepare your remarks. President Lincoln will convene us shortly.',
+    call_to_order: 'President Lincoln calls this meeting to order. Roll call proceeding.',
+    old_business: 'President Lincoln directs we review outstanding items from previous sessions.',
+    current_business: 'Moving to current business. Presenters, you have the floor. Address the President directly.',
+    new_business: 'The floor is now open for new business items. Present to the President.',
+    breakout: 'President Lincoln grants departments leave for brief breakout discussions.',
+    minutes: 'The note taker will now summarize our proceedings for the President.',
+    adjournment: 'With all business concluded, President Lincoln adjourns this session.',
+  };
+  return messages[phase] || 'Proceeding to the next phase.';
+}
diff --git a/src/agents/registry.ts b/src/agents/registry.ts
new file mode 100644
index 0000000..cfd880a
--- /dev/null
+++ b/src/agents/registry.ts
@@ -0,0 +1,148 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Agent Registry
+// All 47 agents + 5 executives ported from config.js
+// ═══════════════════════════════════════════════
+
+import type { Agent, Executive, DeptGroup } from '../types';
+
+// President Lincoln — the owner's podium persona
+// Sir Steven stands at the podium as Abraham Lincoln, the 16th President
+export const BOSS_PERSONA = {
+  name: 'President Lincoln',
+  title: 'Mr. President',
+  fullName: 'Abraham Lincoln',
+  shortRef: 'the President',
+  podiumNote: 'Sir Steven stands at the podium as Abraham Lincoln, the 16th President. Address him formally as "Mr. President" or "President Lincoln".',
+};
+
+export const TEAM_AGENTS: Agent[] = [
+  // Operations
+  { id: 'max', name: 'Ford', pm2: 'dw-master-hub', port: 9893, color: '#B91C1C', role: 'Fleet Commander', dept: 'Operations', hasApi: true },
+  { id: 'hawk', name: 'Sven', pm2: 'dw-dashboard-monitor', port: 9990, color: '#39FF14', role: 'System Monitor', dept: 'Operations', hasApi: true },
+  { id: 'clay', name: 'Seth', pm2: 'agent-cronjob', color: '#64748B', role: 'Scheduler', dept: 'Operations', hasApi: false },
+  { id: 'rex', name: 'Pierce', pm2: 'timeout-agent', color: '#2C3539', role: 'Process Guardian', dept: 'Operations', hasApi: false },
+  { id: 'steve', name: 'Inessa', pm2: 'server-agent', port: 9814, color: '#565672', role: 'Infrastructure', dept: 'Operations', hasApi: true },
+  { id: 'vince', name: 'Vince', pm2: 'web-uptime', port: 9813, color: '#008B8B', role: 'Site Monitor', dept: 'Operations', hasApi: true },
+  { id: 'link', name: 'Cora', pm2: 'dw-central', color: '#0077BE', role: 'Central Coordination', dept: 'Operations', hasApi: false },
+  { id: 'abby', name: 'Abby', pm2: 'abby-agenda', port: 9826, color: '#EC4899', role: 'Agenda Manager', dept: 'Operations', hasApi: false },
+  // Commerce
+  { id: 'shane', name: 'Shane', pm2: 'shopify-expert-agent', port: 9887, color: '#14B8A6', role: 'Shopify Expert', dept: 'Commerce', hasApi: true },
+  // Pipeline (Otto's team)
+  { id: 'clyde', name: 'Silas', pm2: 'sku-check-skill', color: '#84CC16', role: 'SKU Auditor', dept: 'Pipeline', hasApi: false, subMgr: 'otto' },
+  { id: 'dean', name: 'Cole', pm2: 'collections-agent', color: '#00A86B', role: 'Collections', dept: 'Pipeline', hasApi: false, subMgr: 'otto' },
+  { id: 'blake', name: 'Piper', pm2: 'product-scheduler', port: 9600, color: '#6366F1', role: 'Product Scheduler', dept: 'Pipeline', hasApi: true, subMgr: 'otto' },
+  { id: 'clark', name: 'Perry', pm2: 'update-agent', color: '#EA580C', role: 'Product Updates', dept: 'Pipeline', hasApi: false, subMgr: 'otto' },
+  // Finance
+  { id: 'grant', name: 'Finn', pm2: 'dw-accounting', port: 9882, color: '#004225', role: 'Finance', dept: 'Finance', hasApi: true },
+  { id: 'nash', name: 'Eli', pm2: 'dw-expenses-tracker', color: '#047857', role: 'Expenses', dept: 'Finance', hasApi: false },
+  { id: 'hugh', name: 'Phil', pm2: 'dw-purchasing-office', port: 9880, color: '#7C3AED', role: 'Procurement', dept: 'Finance', hasApi: true },
+  { id: 'annie', name: 'Penny', pm2: 'annie-arbitrage', port: 7202, color: '#E879F9', role: 'Pricing Arbitrage', dept: 'Finance', hasApi: true },
+  // Legal
+  { id: 'frank', name: 'Lance', pm2: 'dw-legal-compliance', port: 9878, color: '#374151', role: 'Legal Compliance', dept: 'Legal', hasApi: true },
+  // Content & Marketing
+  { id: 'bob', name: 'Craig', pm2: 'bob-the-blogger', color: '#0EA5E9', role: 'Content Creator', dept: 'Marketing', hasApi: false },
+  { id: 'connie', name: 'Connie', pm2: 'connie-competitive', color: '#EF4444', role: 'Competitive Intel', dept: 'Marketing', hasApi: false },
+  { id: 'sage', name: 'Tara', pm2: 'dw-trend-research', port: 9883, color: '#F59E0B', role: 'Trend Forecaster', dept: 'Marketing', hasApi: true },
+  { id: 'faye', name: 'Rita', pm2: 'furniture-scene-agent', color: '#EC4899', role: 'Room Staging', dept: 'Marketing', hasApi: false },
+  // Catalogs — Heritage (Hugo's team)
+  { id: 'ralph', name: 'Ralph', pm2: 'rl-catalog-9608', port: 9608, color: '#1E40AF', role: 'Ralph Lauren', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  { id: 'sasha', name: 'Sasha', pm2: 'schu-crawler', port: 9965, color: '#8B4513', role: 'Schumacher', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  { id: 'knox', name: 'Knox', pm2: 'krav-crawler', port: 9973, color: '#4B0082', role: 'Kravet', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  { id: 'cleo', name: 'Cleo', pm2: 'cole-crawler', port: 9974, color: '#2F4F4F', role: 'Cole & Son', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  { id: 'elise', name: 'Elise', pm2: 'elit-crawler', port: 9975, color: '#C71585', role: 'Elitis', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  { id: 'artie', name: 'Artie', pm2: 'arte-crawler', port: 9961, color: '#FF4500', role: 'Arte International', dept: 'Catalogs-Heritage', hasApi: true, subMgr: 'hugo' },
+  // Catalogs — Modern (Mack's team)
+  { id: 'kira', name: 'Kira', pm2: 'koro-crawler', port: 9962, color: '#20B2AA', role: 'Koroseal', dept: 'Catalogs-Modern', hasApi: true, subMgr: 'mack' },
+  { id: 'maya', name: 'Maya', pm2: 'maya-crawler', port: 9963, color: '#DA70D6', role: 'Maya Romanoff', dept: 'Catalogs-Modern', hasApi: true, subMgr: 'mack' },
+  { id: 'pj', name: 'PJ', pm2: 'pj-crawler', port: 9968, color: '#DAA520', role: 'Phillip Jeffries', dept: 'Catalogs-Modern', hasApi: true, subMgr: 'mack' },
+  { id: 'ines', name: 'Ines', pm2: 'inno-crawler', port: 9970, color: '#3CB371', role: 'Innovations', dept: 'Catalogs-Modern', hasApi: true, subMgr: 'mack' },
+  { id: 'beau', name: 'Beau', pm2: 'bspk-crawler', port: 9969, color: '#FF8C00', role: 'DW Bespoke', dept: 'Catalogs-Modern', hasApi: true, subMgr: 'mack' },
+  { id: 'tate', name: 'Tate', pm2: 'thibaut-agent', color: '#8FBC8F', role: 'Thibaut Catalog', dept: 'Catalogs-Modern', hasApi: false, subMgr: 'mack' },
+  // Catalogs — National (Ned's team)
+  { id: 'brock', name: 'Brock', pm2: 'brewster-york-catalog', color: '#1E3A5F', role: 'Brewster/York', dept: 'Catalogs-National', hasApi: false, subMgr: 'ned' },
+  { id: 'york', name: 'York', pm2: 'york-contract-catalog', color: '#4682B4', role: 'York Contract', dept: 'Catalogs-National', hasApi: false, subMgr: 'ned' },
+  { id: 'graham', name: 'Graham', pm2: 'graham-brown-catalog', color: '#DC2626', role: 'Graham & Brown', dept: 'Catalogs-National', hasApi: false, subMgr: 'ned' },
+  { id: 'jade', name: 'Grace', pm2: 'gb-agent', color: '#C9A96E', role: 'Glass Bead WP', dept: 'Catalogs-National', hasApi: false, subMgr: 'ned' },
+  { id: 'will', name: 'Will', pm2: 'william-watch-agent', color: '#7B2D8E', role: 'Watch Catalog', dept: 'Catalogs-National', hasApi: false, subMgr: 'ned' },
+  { id: 'scout', name: 'Cyrus', pm2: 'vendor-catalog-scraper', port: 9611, color: '#FF6B35', role: 'Catalog Scraper Engine', dept: 'Catalogs-National', hasApi: true, subMgr: 'ned' },
+  // Catalog Leadership
+  { id: 'atlas', name: 'Porter', pm2: 'product-agent', port: 9604, color: '#7B68EE', role: 'Product Master Viewer', dept: 'Catalogs', hasApi: true },
+  // Data & Communication
+  { id: 'chet', name: 'Chet', pm2: 'slack-dm-viewer', port: 7204, color: '#4A154B', role: 'Communications', dept: 'Data', hasApi: true },
+  { id: 'george', name: 'George', pm2: 'george-gmail', port: 9850, color: '#EA4335', role: 'Gmail Agent', dept: 'Data', hasApi: true },
+  { id: 'ken', name: 'Trent', pm2: 'ken', color: '#FF69B4', role: 'Trading Intelligence', dept: 'Data', hasApi: false },
+  { id: 'nate', name: 'Nate', pm2: 'commit-agent', color: '#059669', role: 'Note Taker & Records', dept: 'Data', hasApi: false, isNoteTaker: true },
+  { id: 'ross', name: 'Arlo', pm2: 'dw-agent-directory', port: 9891, color: '#8F8F8F', role: 'Agent Directory', dept: 'Data', hasApi: true },
+  // Imagery
+  { id: 'drew', name: 'Drew', pm2: 'dw-digital-samples', port: 9879, color: '#06B6D4', role: 'Digital Samples', dept: 'Imagery', hasApi: true },
+  { id: 'chase', name: 'Miles', pm2: 'no-image-agent', color: '#F87171', role: 'Missing Images', dept: 'Imagery', hasApi: false },
+  // Social Media
+  { id: 'iris', name: 'Iris', pm2: 'instagram-agent', port: 9870, color: '#E1306C', role: 'Instagram Agent', dept: 'Marketing', hasApi: true },
+];
+
+export const EXECUTIVES: Executive[] = [
+  { id: 'ceo', name: 'Curtis', port: 7120, color: '#5B21B6', role: 'Chief Executive', avatar: 'curtis' },
+  { id: 'cfo', name: 'Cliff', port: 7121, color: '#065F46', role: 'Chief Financial Officer', avatar: 'cliff' },
+  { id: 'coo', name: 'Conrad', port: 7122, color: '#CD7F32', role: 'Chief Operations Officer', avatar: 'conrad' },
+  { id: 'cto', name: 'Troy', port: 7124, color: '#9B111E', role: 'Chief Technology Officer', avatar: 'troy' },
+  { id: 'vp-ops', name: 'Vaughn', port: 7125, color: '#6D28D9', role: 'VP of Operations', avatar: 'vaughn' },
+];
+
+export const DEPARTMENT_GROUPS: Record<string, DeptGroup> = {
+  'Operations':        { emoji: '\u2699\uFE0F', color: '#B91C1C', leader: 'max',    exec: 'coo',   label: 'Ops Command' },
+  'Commerce':          { emoji: '\uD83D\uDED2', color: '#14B8A6', leader: 'shane',  exec: 'cfo',   label: 'Commerce' },
+  'Finance':           { emoji: '\uD83D\uDCB0', color: '#004225', leader: 'grant',  exec: 'cfo',   label: 'Finance' },
+  'Legal':             { emoji: '\u2696\uFE0F', color: '#374151', leader: 'frank',  exec: 'ceo',   label: 'Legal' },
+  'Marketing':         { emoji: '\uD83D\uDCE3', color: '#F59E0B', leader: 'sage',   exec: 'vpops', label: 'Content & Staging' },
+  'Catalogs':          { emoji: '\uD83D\uDCE6', color: '#1E3A5F', leader: 'atlas',  exec: 'vpops', label: 'Catalog Leadership' },
+  'Catalogs-Heritage': { emoji: '\uD83C\uDFDB\uFE0F', color: '#8B4513', leader: 'ralph', exec: 'vpops', label: 'Heritage Vendors' },
+  'Catalogs-Modern':   { emoji: '\uD83D\uDE80', color: '#20B2AA', leader: 'kira',   exec: 'vpops', label: 'Modern Vendors' },
+  'Catalogs-National': { emoji: '\uD83C\uDDFA\uD83C\uDDF8', color: '#4682B4', leader: 'brock', exec: 'vpops', label: 'National Vendors' },
+  'Pipeline':          { emoji: '\u26A1',       color: '#6366F1', leader: 'blake',  exec: 'vpops', label: 'Ops Pipeline' },
+  'Data':              { emoji: '\uD83D\uDCCA', color: '#059669', leader: 'nate',   exec: 'cto',   label: 'Data & Intel' },
+  'Imagery':           { emoji: '\uD83D\uDDBC\uFE0F', color: '#06B6D4', leader: 'drew',   exec: 'vpops', label: 'Imagery' },
+};
+
+// Cross-department questioners — agents who ask questions about other dept reports
+export const CROSS_DEPT_QUESTIONERS: Record<string, string[]> = {
+  'Operations': ['shane', 'grant', 'frank'],
+  'Commerce':   ['hawk', 'grant', 'drew', 'frank'],
+  'Finance':    ['shane', 'hugh', 'max'],
+  'Legal':      ['shane', 'bob', 'max'],
+  'Marketing':  ['shane', 'dean', 'sage'],
+  'Catalogs':   ['shane', 'drew', 'dean', 'clark'],
+  'Data':       ['max', 'hawk', 'shane'],
+  'Imagery':    ['shane', 'dean', 'faye'],
+};
+
+// Helper functions
+export function getAgent(id: string): Agent | undefined {
+  return TEAM_AGENTS.find(a => a.id === id);
+}
+
+export function getExec(id: string): Executive | undefined {
+  return EXECUTIVES.find(e => e.id === id);
+}
+
+export function getAgentsByDept(dept: string): Agent[] {
+  return TEAM_AGENTS.filter(a => a.dept === dept);
+}
+
+export function getAgentsByExec(execId: string): Agent[] {
+  const depts = Object.entries(DEPARTMENT_GROUPS)
+    .filter(([, g]) => g.exec === execId)
+    .map(([name]) => name);
+  return TEAM_AGENTS.filter(a => depts.includes(a.dept));
+}
+
+export function getTeamsByExec(): Record<string, { exec: Executive; agents: Agent[] }> {
+  const result: Record<string, { exec: Executive; agents: Agent[] }> = {};
+  for (const exec of EXECUTIVES) {
+    result[exec.id] = { exec, agents: getAgentsByExec(exec.id) };
+  }
+  return result;
+}
+
+export function getNoteTaker(): Agent | undefined {
+  return TEAM_AGENTS.find(a => a.isNoteTaker);
+}
diff --git a/src/agents/voices.ts b/src/agents/voices.ts
new file mode 100644
index 0000000..6f66e37
--- /dev/null
+++ b/src/agents/voices.ts
@@ -0,0 +1,79 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Neural TTS Voice Map
+// Microsoft Edge Neural Voices via edge-tts
+// ═══════════════════════════════════════════════
+
+export const NEURAL_VOICES: Record<string, string> = {
+  // Executives — authoritative, distinct
+  'ceo':     'en-US-AndrewNeural',
+  'cfo':     'en-GB-RyanNeural',
+  'coo':     'en-US-ChristopherNeural',
+  'cto':     'en-GB-ThomasNeural',
+  'vp-ops':  'en-AU-WilliamMultilingualNeural',
+  // Operations
+  'max':     'en-US-SteffanNeural',
+  'hawk':    'en-US-RogerNeural',
+  'clay':    'en-US-BrianNeural',
+  'rex':     'en-IE-ConnorNeural',
+  'steve':   'en-US-JennyNeural',
+  'vince':   'en-US-EricNeural',
+  'link':    'en-US-BrianMultilingualNeural',
+  'abby':    'en-US-JennyNeural',
+  // Commerce
+  'shane':   'en-US-AndrewMultilingualNeural',
+  'clyde':   'en-US-ChristopherNeural',
+  'dean':    'en-GB-ThomasNeural',
+  'blake':   'en-US-RogerNeural',
+  // Finance
+  'grant':   'en-US-SteffanNeural',
+  'nash':    'en-IE-ConnorNeural',
+  'hugh':    'en-GB-RyanNeural',
+  'annie':   'en-US-EmmaNeural',
+  // Legal
+  'frank':   'en-US-EricNeural',
+  // Marketing
+  'bob':     'en-US-GuyNeural',
+  'connie':  'en-US-AvaNeural',
+  'sage':    'en-US-AriaNeural',
+  'faye':    'en-US-MichelleNeural',
+  // Catalogs
+  'brock':   'en-US-BrianNeural',
+  'graham':  'en-GB-RyanNeural',
+  'york':    'en-US-AndrewNeural',
+  'ralph':   'en-US-ChristopherNeural',
+  'jade':    'en-GB-LibbyNeural',
+  'tate':    'en-AU-WilliamMultilingualNeural',
+  'sasha':   'en-US-AriaNeural',
+  'knox':    'en-US-SteffanNeural',
+  'cleo':    'en-GB-LibbyNeural',
+  'elise':   'en-US-MichelleNeural',
+  'artie':   'en-US-GuyNeural',
+  'kira':    'en-US-EmmaNeural',
+  'maya':    'en-US-AvaNeural',
+  'pj':      'en-US-RogerNeural',
+  'ines':    'en-US-JennyNeural',
+  'beau':    'en-US-BrianNeural',
+  'scout':   'en-US-EricNeural',
+  'atlas':   'en-US-AndrewNeural',
+  'will':    'en-GB-ThomasNeural',
+  // Data & Comms
+  'nate':    'en-US-BrianNeural',
+  'ross':    'en-US-AndrewMultilingualNeural',
+  'chet':    'en-IE-ConnorNeural',
+  'ken':     'en-US-RogerNeural',
+  'george':  'en-US-SteffanNeural',
+  'iris':    'en-US-AvaNeural',
+  // Imagery
+  'drew':    'en-US-GuyNeural',
+  'chase':   'en-US-SteffanNeural',
+  'clark':   'en-US-EricNeural',
+  // System defaults
+  'user':       'en-US-AndrewNeural',
+  'boardroom':  'en-US-AvaMultilingualNeural',
+  'system':     'en-US-AvaMultilingualNeural',
+  'default':    'en-US-AriaNeural',
+};
+
+export function getVoice(agentId: string): string {
+  return NEURAL_VOICES[agentId] || NEURAL_VOICES['default'];
+}
diff --git a/src/api/middleware/auth.ts b/src/api/middleware/auth.ts
new file mode 100644
index 0000000..c171b72
--- /dev/null
+++ b/src/api/middleware/auth.ts
@@ -0,0 +1,25 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Basic Auth Middleware
+// ═══════════════════════════════════════════════
+
+import type { Request, Response, NextFunction } from 'express';
+
+const AUTH_USER = process.env.AUTH_USER || 'admin';
+const AUTH_PASS = process.env.AUTH_PASS || 'REDACTED_PASSWORD';
+
+export function basicAuth(req: Request, res: Response, next: NextFunction): void {
+  const authHeader = req.headers.authorization;
+  if (!authHeader || !authHeader.startsWith('Basic ')) {
+    res.status(401).json({ error: 'Authentication required' });
+    return;
+  }
+
+  const decoded = Buffer.from(authHeader.slice(6), 'base64').toString();
+  const [user, pass] = decoded.split(':');
+
+  if (user === AUTH_USER && pass === AUTH_PASS) {
+    next();
+  } else {
+    res.status(401).json({ error: 'Invalid credentials' });
+  }
+}
diff --git a/src/api/routes/agenda.ts b/src/api/routes/agenda.ts
new file mode 100644
index 0000000..9e50af7
--- /dev/null
+++ b/src/api/routes/agenda.ts
@@ -0,0 +1,65 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Agenda Routes
+// ═══════════════════════════════════════════════
+
+import { Router } from 'express';
+import * as engine from '../../engine/meetingEngine';
+import { validateAgenda } from '../../agents/president';
+
+const router = Router();
+
+// GET /api/agenda/:meetingId — get items
+router.get('/:meetingId', (req, res) => {
+  try {
+    const items = engine.getAgendaItems(req.params.meetingId);
+    res.json(items);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/agenda/:meetingId — add item
+router.post('/:meetingId', (req, res) => {
+  try {
+    const { topic, recommendation, decisionType, owner } = req.body;
+    if (!topic) {
+      res.status(400).json({ error: 'Topic is required' });
+      return;
+    }
+
+    const validation = validateAgenda([{ topic, decision_type: decisionType }]);
+    if (!validation.valid) {
+      res.status(400).json({ error: validation.errors.join('; ') });
+      return;
+    }
+
+    const item = engine.addAgendaItem(req.params.meetingId, topic, recommendation, decisionType, owner);
+    res.json(item);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// PUT /api/agenda/:meetingId/:itemId — update item
+router.put('/:meetingId/:itemId', (req, res) => {
+  try {
+    const itemId = parseInt(req.params.itemId);
+    const item = engine.updateAgendaItem(req.params.meetingId, itemId, req.body);
+    res.json(item);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// DELETE /api/agenda/:meetingId/:itemId — delete item
+router.delete('/:meetingId/:itemId', (req, res) => {
+  try {
+    const itemId = parseInt(req.params.itemId);
+    engine.deleteAgendaItem(req.params.meetingId, itemId);
+    res.json({ deleted: true });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/agents.ts b/src/api/routes/agents.ts
new file mode 100644
index 0000000..30d3171
--- /dev/null
+++ b/src/api/routes/agents.ts
@@ -0,0 +1,67 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Agent Routes
+// ═══════════════════════════════════════════════
+
+import { Router } from 'express';
+import { TEAM_AGENTS, EXECUTIVES, getAgent, getExec, getAgentsByDept, getTeamsByExec, DEPARTMENT_GROUPS } from '../../agents/registry';
+import { generateDialogue } from '../../engine/agentDialogue';
+
+const router = Router();
+
+// GET /api/agents — full roster
+router.get('/', (req, res) => {
+  const dept = req.query.dept as string | undefined;
+  let agents = dept ? getAgentsByDept(dept) : TEAM_AGENTS;
+  res.json({
+    agents,
+    executives: EXECUTIVES,
+    total: agents.length,
+    departments: Object.keys(DEPARTMENT_GROUPS),
+  });
+});
+
+// GET /api/agents/teams — grouped by exec
+router.get('/teams', (req, res) => {
+  const teams = getTeamsByExec();
+  res.json(teams);
+});
+
+// GET /api/agents/:name — agent detail
+router.get('/:name', (req, res) => {
+  const agent = getAgent(req.params.name) || getExec(req.params.name);
+  if (!agent) {
+    res.status(404).json({ error: 'Agent not found' });
+    return;
+  }
+  res.json(agent);
+});
+
+// POST /api/agents/:name/speak — generate dialogue
+router.post('/:name/speak', async (req, res) => {
+  try {
+    const agent = getAgent(req.params.name);
+    const exec = getExec(req.params.name);
+    const entity = agent || exec;
+
+    if (!entity) {
+      res.status(404).json({ error: 'Agent not found' });
+      return;
+    }
+
+    const { topic, phase, meetingType } = req.body;
+    const dialogue = await generateDialogue(
+      entity.id || req.params.name,
+      entity.name,
+      entity.role,
+      phase || 'current_business',
+      topic || 'General discussion',
+      meetingType || 'strategic'
+    );
+
+    res.json({ agent: entity.name, dialogue });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/health.ts b/src/api/routes/health.ts
new file mode 100644
index 0000000..62b1def
--- /dev/null
+++ b/src/api/routes/health.ts
@@ -0,0 +1,45 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Health & Status Routes
+// ═══════════════════════════════════════════════
+
+import { Router } from 'express';
+import { getActiveMeeting, listMeetings } from '../../engine/meetingEngine';
+import { TEAM_AGENTS, EXECUTIVES } from '../../agents/registry';
+import { isGovernanceAvailable } from '../../engine/governanceClient';
+import { getConnectionCount } from '../../ws/broadcast';
+
+const router = Router();
+const startTime = Date.now();
+
+// GET /api/status — detailed status
+router.get('/status', async (req, res) => {
+  try {
+    const govAvailable = await isGovernanceAvailable();
+    const activeMeeting = getActiveMeeting();
+    const recentMeetings = listMeetings(5);
+
+    res.json({
+      service: 'dw-boardroom-v2',
+      version: '1.0.0',
+      uptime: Math.floor((Date.now() - startTime) / 1000),
+      agents: TEAM_AGENTS.length,
+      executives: EXECUTIVES.length,
+      wsClients: getConnectionCount(),
+      activeMeeting: activeMeeting ? {
+        id: activeMeeting.id,
+        type: activeMeeting.meeting_type,
+        phase: activeMeeting.phase,
+        status: activeMeeting.status,
+      } : null,
+      recentMeetings: recentMeetings.length,
+      governance: {
+        url: process.env.GOVERNANCE_URL || 'http://127.0.0.1:4020',
+        available: govAvailable,
+      },
+    });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/meetings.ts b/src/api/routes/meetings.ts
new file mode 100644
index 0000000..9adc9dd
--- /dev/null
+++ b/src/api/routes/meetings.ts
@@ -0,0 +1,121 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Meeting Routes
+// ═══════════════════════════════════════════════
+
+import { Router } from 'express';
+import * as engine from '../../engine/meetingEngine';
+import type { MeetingType } from '../../types';
+
+const router = Router();
+
+// GET /api/meetings — list all meetings
+router.get('/', (req, res) => {
+  try {
+    const limit = parseInt(req.query.limit as string) || 50;
+    const meetings = engine.listMeetings(limit);
+    res.json(meetings);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/active — current meeting
+router.get('/active', (req, res) => {
+  try {
+    const meeting = engine.getActiveMeeting();
+    if (!meeting) {
+      res.json({ active: false, meeting: null });
+      return;
+    }
+    const messages = engine.getMeetingMessages(meeting.id, 100);
+    const agenda = engine.getAgendaItems(meeting.id);
+    res.json({ active: true, meeting, messages, agenda });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/:id — meeting detail
+router.get('/:id', (req, res) => {
+  try {
+    const meeting = engine.getMeeting(req.params.id);
+    if (!meeting) {
+      res.status(404).json({ error: 'Meeting not found' });
+      return;
+    }
+    const messages = engine.getMeetingMessages(meeting.id);
+    const agenda = engine.getAgendaItems(meeting.id);
+    res.json({ meeting, messages, agenda });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/start/:type — start meeting
+router.post('/start/:type', async (req, res) => {
+  try {
+    const type = req.params.type as MeetingType;
+    if (!['strategic', 'initiative', 'ops', 'emergency'].includes(type)) {
+      res.status(400).json({ error: 'Invalid meeting type. Use: strategic, initiative, ops, emergency' });
+      return;
+    }
+    const meeting = await engine.startMeeting(type);
+    res.json(meeting);
+  } catch (err: any) {
+    res.status(409).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/halt — halt meeting
+router.post('/:id/halt', (req, res) => {
+  try {
+    const meeting = engine.haltMeeting(req.params.id);
+    res.json(meeting);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/advance — advance phase
+router.post('/:id/advance', async (req, res) => {
+  try {
+    const meeting = await engine.advancePhase(req.params.id);
+    res.json(meeting);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/message — add message
+router.post('/:id/message', (req, res) => {
+  try {
+    const { agentId, agentName, message, messageType } = req.body;
+    if (!message) {
+      res.status(400).json({ error: 'Message required' });
+      return;
+    }
+    const msg = engine.addMessage(
+      req.params.id,
+      agentId || 'user',
+      agentName || 'User',
+      message,
+      messageType || 'dialogue'
+    );
+    res.json(msg);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/:id/messages — get messages
+router.get('/:id/messages', (req, res) => {
+  try {
+    const limit = parseInt(req.query.limit as string) || 500;
+    const messages = engine.getMeetingMessages(req.params.id, limit);
+    res.json(messages);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/voice.ts b/src/api/routes/voice.ts
new file mode 100644
index 0000000..7f14d00
--- /dev/null
+++ b/src/api/routes/voice.ts
@@ -0,0 +1,51 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Voice / TTS Routes
+// ═══════════════════════════════════════════════
+
+import { Router } from 'express';
+import { queryOne, run, query } from '../../db/sqlite';
+import { getVoice } from '../../agents/voices';
+
+const router = Router();
+
+// GET /api/voice/config — TTS settings
+router.get('/config', (req, res) => {
+  try {
+    const rows = query<{ key: string; value: string }>('SELECT key, value FROM br2_voice_config');
+    const config: Record<string, any> = {};
+    for (const row of rows) {
+      if (row.value === 'true') config[row.key] = true;
+      else if (row.value === 'false') config[row.key] = false;
+      else if (!isNaN(Number(row.value))) config[row.key] = Number(row.value);
+      else config[row.key] = row.value;
+    }
+    res.json(config);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/voice/config — update TTS settings
+router.post('/config', (req, res) => {
+  try {
+    const updates = req.body;
+    for (const [key, value] of Object.entries(updates)) {
+      run(
+        `INSERT INTO br2_voice_config (key, value, updated_at) VALUES (?, ?, datetime('now'))
+         ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`,
+        [key, String(value), String(value)]
+      );
+    }
+    res.json({ updated: true });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/voice/agent/:id — get voice for agent
+router.get('/agent/:id', (req, res) => {
+  const voice = getVoice(req.params.id);
+  res.json({ agentId: req.params.id, voice });
+});
+
+export default router;
diff --git a/src/api/server.ts b/src/api/server.ts
new file mode 100644
index 0000000..8f0b0e9
--- /dev/null
+++ b/src/api/server.ts
@@ -0,0 +1,79 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Express Server
+// Meeting orchestration with 47 AI agents
+// Port 4040 — API + WebSocket
+// ═══════════════════════════════════════════════
+
+import dotenv from 'dotenv';
+dotenv.config();
+
+import express from 'express';
+import cors from 'cors';
+import http from 'http';
+import { basicAuth } from './middleware/auth';
+import { initWebSocket } from '../ws/broadcast';
+import { runMigration } from '../db/migrate';
+
+// Route imports
+import meetingRoutes from './routes/meetings';
+import agentRoutes from './routes/agents';
+import agendaRoutes from './routes/agenda';
+import voiceRoutes from './routes/voice';
+import healthRoutes from './routes/health';
+
+const PORT = parseInt(process.env.PORT || '4040');
+const FRONTEND_URL = process.env.FRONTEND_URL || 'http://45.61.58.125:4050';
+
+// ─── Initialize ────────────────────────────────
+
+// Run SQLite migration
+runMigration();
+
+const app = express();
+const server = http.createServer(app);
+
+// Middleware
+app.use(cors({
+  origin: [FRONTEND_URL, 'http://127.0.0.1:4050', 'http://localhost:4050', 'http://45.61.58.125:4060'],
+  credentials: true,
+}));
+app.use(express.json({ limit: '1mb' }));
+
+// Health check — no auth
+app.get('/health', (req, res) => {
+  res.json({ status: 'ok', service: 'dw-boardroom-v2', timestamp: new Date().toISOString() });
+});
+
+// Protected routes
+app.use('/api/meetings', basicAuth, meetingRoutes);
+app.use('/api/agents', basicAuth, agentRoutes);
+app.use('/api/agenda', basicAuth, agendaRoutes);
+app.use('/api/voice', basicAuth, voiceRoutes);
+app.use('/api', basicAuth, healthRoutes);
+
+// ─── WebSocket ─────────────────────────────────
+
+initWebSocket(server);
+
+// ─── Start ─────────────────────────────────────
+
+server.listen(PORT, '0.0.0.0', () => {
+  console.log(`\n═══════════════════════════════════════════════`);
+  console.log(`  DW Boardroom V2`);
+  console.log(`  API:       http://45.61.58.125:${PORT}`);
+  console.log(`  WebSocket: ws://45.61.58.125:${PORT}/ws`);
+  console.log(`  Frontend:  ${FRONTEND_URL}`);
+  console.log(`  Governance: ${process.env.GOVERNANCE_URL || 'http://127.0.0.1:4020'}`);
+  console.log(`═══════════════════════════════════════════════\n`);
+});
+
+// Graceful shutdown
+process.on('SIGTERM', () => {
+  console.log('[server] SIGTERM received, shutting down...');
+  server.close(() => process.exit(0));
+});
+
+process.on('SIGINT', () => {
+  console.log('[server] SIGINT received, shutting down...');
+  server.close(() => process.exit(0));
+});
diff --git a/src/db/migrate.ts b/src/db/migrate.ts
new file mode 100644
index 0000000..c55376b
--- /dev/null
+++ b/src/db/migrate.ts
@@ -0,0 +1,37 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — SQLite Migration Runner
+// ═══════════════════════════════════════════════
+
+import fs from 'fs';
+import path from 'path';
+import { exec, getDb } from './sqlite';
+
+export function runMigration(): void {
+  const schemaPath = path.join(__dirname, '..', '..', 'src', 'db', 'schema.sql');
+
+  // When running from dist/, adjust path
+  const resolvedPath = fs.existsSync(schemaPath)
+    ? schemaPath
+    : path.join(__dirname, '..', '..', '..', 'src', 'db', 'schema.sql');
+
+  if (!fs.existsSync(resolvedPath)) {
+    throw new Error(`Schema file not found at ${schemaPath} or ${resolvedPath}`);
+  }
+
+  const schema = fs.readFileSync(resolvedPath, 'utf-8');
+  exec(schema);
+
+  // Verify tables exist
+  const tables = getDb()
+    .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'br2_%'")
+    .all() as { name: string }[];
+
+  console.log(`[migrate] Created/verified ${tables.length} tables:`, tables.map(t => t.name).join(', '));
+}
+
+// Run directly if called as script
+if (require.main === module) {
+  runMigration();
+  console.log('[migrate] Done.');
+  process.exit(0);
+}
diff --git a/src/db/schema.sql b/src/db/schema.sql
new file mode 100644
index 0000000..f6ff691
--- /dev/null
+++ b/src/db/schema.sql
@@ -0,0 +1,58 @@
+-- ═══════════════════════════════════════════════
+-- DW Boardroom V2 — SQLite Schema
+-- Ephemeral meeting data (persistent governance data via API on port 4020)
+-- ═══════════════════════════════════════════════
+
+CREATE TABLE IF NOT EXISTS br2_meetings (
+  id TEXT PRIMARY KEY,
+  meeting_type TEXT NOT NULL CHECK(meeting_type IN ('strategic','initiative','ops','emergency')),
+  phase TEXT NOT NULL DEFAULT 'caucus',
+  status TEXT NOT NULL DEFAULT 'scheduled' CHECK(status IN ('scheduled','in_progress','completed','halted','crashed')),
+  started_at TEXT,
+  ended_at TEXT,
+  summary TEXT,
+  attendees TEXT DEFAULT '[]',        -- JSON array of agent IDs
+  action_items TEXT DEFAULT '[]',     -- JSON array of action item objects
+  created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+CREATE TABLE IF NOT EXISTS br2_messages (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  meeting_id TEXT NOT NULL REFERENCES br2_meetings(id),
+  agent_id TEXT NOT NULL,
+  agent_name TEXT NOT NULL,
+  message TEXT NOT NULL,
+  message_type TEXT NOT NULL DEFAULT 'dialogue' CHECK(message_type IN ('dialogue','phase','system','action','decision')),
+  phase TEXT,
+  created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+CREATE TABLE IF NOT EXISTS br2_agenda (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  meeting_id TEXT NOT NULL REFERENCES br2_meetings(id),
+  topic TEXT NOT NULL,
+  recommendation TEXT,
+  decision_type TEXT DEFAULT 'B' CHECK(decision_type IN ('A','B','C')),
+  owner TEXT,
+  position INTEGER NOT NULL DEFAULT 0,
+  status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','discussed','resolved','deferred')),
+  created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+CREATE TABLE IF NOT EXISTS br2_voice_config (
+  key TEXT PRIMARY KEY,
+  value TEXT NOT NULL,
+  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_br2_messages_meeting ON br2_messages(meeting_id);
+CREATE INDEX IF NOT EXISTS idx_br2_messages_agent ON br2_messages(agent_id);
+CREATE INDEX IF NOT EXISTS idx_br2_agenda_meeting ON br2_agenda(meeting_id);
+CREATE INDEX IF NOT EXISTS idx_br2_meetings_status ON br2_meetings(status);
+
+-- Default voice config
+INSERT OR IGNORE INTO br2_voice_config (key, value) VALUES ('tts_enabled', 'false');
+INSERT OR IGNORE INTO br2_voice_config (key, value) VALUES ('tts_speed', '1.0');
+INSERT OR IGNORE INTO br2_voice_config (key, value) VALUES ('tts_volume', '0.8');
+INSERT OR IGNORE INTO br2_voice_config (key, value) VALUES ('auto_speak', 'false');
diff --git a/src/db/sqlite.ts b/src/db/sqlite.ts
new file mode 100644
index 0000000..ed2af86
--- /dev/null
+++ b/src/db/sqlite.ts
@@ -0,0 +1,45 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — SQLite Database Wrapper
+// Ephemeral meeting data stored locally
+// ═══════════════════════════════════════════════
+
+import Database from 'better-sqlite3';
+import path from 'path';
+
+const DB_PATH = path.join(__dirname, '..', '..', 'boardroom.db');
+
+let db: Database.Database;
+
+export function getDb(): Database.Database {
+  if (!db) {
+    db = new Database(DB_PATH);
+    db.pragma('journal_mode = WAL');
+    db.pragma('foreign_keys = ON');
+  }
+  return db;
+}
+
+export function query<T = any>(sql: string, params: any[] = []): T[] {
+  const stmt = getDb().prepare(sql);
+  return stmt.all(...params) as T[];
+}
+
+export function queryOne<T = any>(sql: string, params: any[] = []): T | undefined {
+  const stmt = getDb().prepare(sql);
+  return stmt.get(...params) as T | undefined;
+}
+
+export function run(sql: string, params: any[] = []): Database.RunResult {
+  const stmt = getDb().prepare(sql);
+  return stmt.run(...params);
+}
+
+export function exec(sql: string): void {
+  getDb().exec(sql);
+}
+
+export function closeDb(): void {
+  if (db) {
+    db.close();
+  }
+}
diff --git a/src/engine/agentDialogue.ts b/src/engine/agentDialogue.ts
new file mode 100644
index 0000000..d366285
--- /dev/null
+++ b/src/engine/agentDialogue.ts
@@ -0,0 +1,153 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Gemini Agent Dialogue
+// Generates in-character speech for 47 agents
+// Anti-hallucination guards from existing boardroom
+// ═══════════════════════════════════════════════
+
+import fetch from 'node-fetch';
+
+const GEMINI_KEY = process.env.GEMINI_KEY || '';
+const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
+
+// Anti-hallucination patterns — ported from existing boardroom
+const HALLUCINATION_PATTERNS = [
+  /(?:I (?:just |recently )?(?:completed|finished|resolved|fixed|addressed|updated|processed|handled|executed|fulfilled)\b)/i,
+  /(?:I (?:have |'ve )?(?:sent|dispatched|delivered|forwarded|transmitted|posted)\b)/i,
+  /(?:(?:all|every) (?:systems?|services?|agents?|processes?) (?:are |is )?(?:operational|running|online|healthy|stable|green)\b)/i,
+  /(?:I (?:am )?(?:currently |actively )?(?:monitoring|tracking|watching|observing|analyzing)\b)/i,
+  /(?:(?:revenue|sales|profit|income|earnings) (?:is |are )?(?:up|increased|growing|improved)\b)/i,
+  /(?:I (?:have |'ve )?(?:detected|discovered|found|identified|noticed|observed)\b)/i,
+  /(?:(?:task|issue|problem|bug|ticket) (?:has been |was )?(?:resolved|fixed|closed|completed|addressed)\b)/i,
+  /(?:I (?:can |will |shall )?(?:confirm|verify|validate|certify|attest)\b)/i,
+  /(?:(?:email|message|notification|alert) (?:has been |was )?(?:sent|delivered|dispatched)\b)/i,
+  /(?:(?:database|server|system|service) (?:is )?(?:fully )?(?:optimized|upgraded|migrated|updated)\b)/i,
+  /(?:(?:security|compliance|audit) (?:check|scan|review) (?:passed|cleared|completed)\b)/i,
+  /(?:I (?:have |'ve )?(?:negotiated|secured|obtained|arranged|organized)\b)/i,
+  /(?:(?:inventory|stock|supply) (?:has been |is )?(?:replenished|restocked|updated|synchronized)\b)/i,
+  /(?:(?:customer|client|user) (?:satisfaction|feedback|rating) (?:is )?(?:excellent|positive|improving)\b)/i,
+  /(?:I (?:have |'ve )?(?:already|previously) (?:submitted|filed|reported|logged)\b)/i,
+];
+
+// Reality anchor — prevents Gemini from fabricating actions
+const REALITY_ANCHOR = `CRITICAL RULES:
+1. You are an AI agent in a boardroom simulation. You do NOT have access to real systems.
+2. NEVER claim to have completed actions, sent emails, fixed bugs, or monitored systems.
+3. NEVER fabricate data, metrics, numbers, or statistics.
+4. Instead: propose actions, recommend strategies, identify concerns, ask questions.
+5. Use phrases like "I recommend...", "We should consider...", "My concern is...", "I propose..."
+6. Keep response to EXACTLY 1 SHORT sentence. Blunt and direct. No fluff.
+7. Stay in character for your role but never claim to have done real work.
+8. PERSONA: Sir Steven stands at the podium as Abraham Lincoln, the 16th President. Address him as "Mr. President" or "President Lincoln".`;
+
+export async function generateDialogue(
+  agentId: string,
+  agentName: string,
+  role: string,
+  phase: string,
+  context: string,
+  meetingType: string
+): Promise<string> {
+  // Fallback if no API key
+  if (!GEMINI_KEY) {
+    return getFallbackDialogue(agentName, role, phase, context);
+  }
+
+  const prompt = `${REALITY_ANCHOR}
+
+You are ${agentName}, the ${role} at Designer Wallcoverings.
+Meeting type: ${meetingType}
+Current phase: ${phase}
+Context: ${context}
+
+Respond in EXACTLY 1 short blunt sentence. Direct. No filler words.`;
+
+  try {
+    const res = await fetch(GEMINI_URL, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        contents: [{ parts: [{ text: prompt }] }],
+        generationConfig: {
+          maxOutputTokens: 60,
+          temperature: 0.7,
+        },
+      }),
+    });
+
+    if (!res.ok) {
+      console.error(`[agentDialogue] Gemini ${res.status}`);
+      return getFallbackDialogue(agentName, role, phase, context);
+    }
+
+    const data = await res.json() as any;
+    let text = data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
+
+    // Anti-hallucination filter
+    text = filterHallucinations(text, agentName, role);
+
+    return text || getFallbackDialogue(agentName, role, phase, context);
+  } catch (err: any) {
+    console.error(`[agentDialogue] Error:`, err.message);
+    return getFallbackDialogue(agentName, role, phase, context);
+  }
+}
+
+function filterHallucinations(text: string, agentName: string, role: string): string {
+  let filtered = text;
+
+  for (const pattern of HALLUCINATION_PATTERNS) {
+    if (pattern.test(filtered)) {
+      // Replace claims of action with recommendations
+      filtered = filtered
+        .replace(/I (?:just |recently )?(?:completed|finished|resolved|fixed)/gi, 'I recommend we address')
+        .replace(/I (?:have |'ve )?(?:sent|dispatched|delivered|forwarded)/gi, 'I suggest we send')
+        .replace(/(?:all|every) (?:systems?|services?) (?:are |is )?(?:operational|running|online)/gi, 'systems should be checked to confirm they are operational')
+        .replace(/I (?:am )?(?:currently |actively )?monitoring/gi, 'I recommend monitoring')
+        .replace(/(?:revenue|sales) (?:is |are )?(?:up|increased|growing)/gi, 'revenue trends should be reviewed')
+        .replace(/I (?:have |'ve )?(?:detected|discovered|found)/gi, 'We should investigate potential')
+        .replace(/(?:task|issue) (?:has been |was )?(?:resolved|fixed|closed)/gi, 'the task needs verification to confirm it is resolved');
+    }
+  }
+
+  return filtered.trim();
+}
+
+function getFallbackDialogue(agentName: string, role: string, phase: string, context: string): string {
+  const fallbacks: Record<string, string[]> = {
+    caucus: [
+      `${agentName} here, ready for the session.`,
+      `${agentName} present. Let's make this productive.`,
+    ],
+    call_to_order: [
+      `${agentName}, ${role}, standing by.`,
+      `Present and accounted for. ${agentName} ready to contribute.`,
+    ],
+    old_business: [
+      `From the ${role} perspective, I recommend we review outstanding items before proceeding.`,
+      `I suggest we verify completion of prior action items related to ${role.toLowerCase()}.`,
+    ],
+    current_business: [
+      `Regarding ${context.substring(0, 50)}... I recommend we evaluate this carefully.`,
+      `From my vantage as ${role}, this deserves thorough consideration.`,
+    ],
+    new_business: [
+      `I'd like to raise a point about ${role.toLowerCase()} priorities going forward.`,
+      `For new business, I propose we consider our ${role.toLowerCase()} strategy.`,
+    ],
+    breakout: [
+      `My team has several items to discuss offline. Will report back.`,
+      `We'll coordinate on the ${role.toLowerCase()} items and bring a summary.`,
+    ],
+    minutes: [
+      `Key takeaways noted for the record.`,
+      `I'll ensure all action items are properly documented.`,
+    ],
+    adjournment: [
+      `Good session. Looking forward to executing on these decisions.`,
+      `Thank you all. ${agentName} signing off.`,
+    ],
+  };
+
+  const options = fallbacks[phase] || [`${agentName} acknowledges.`];
+  return options[Math.floor(Math.random() * options.length)];
+}
diff --git a/src/engine/governanceClient.ts b/src/engine/governanceClient.ts
new file mode 100644
index 0000000..ef2f8a7
--- /dev/null
+++ b/src/engine/governanceClient.ts
@@ -0,0 +1,128 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Governance API Client
+// All persistent governance data flows through port 4020
+// ═══════════════════════════════════════════════
+
+import fetch from 'node-fetch';
+import type { GovDecision, GovDelegation, GovEscalation, GovStatus, GovDecisionStats, DecisionType } from '../types';
+
+const GOVERNANCE_URL = process.env.GOVERNANCE_URL || 'http://127.0.0.1:4020';
+const AUTH = Buffer.from(`${process.env.AUTH_USER || 'admin'}:${process.env.AUTH_PASS || 'REDACTED_PASSWORD'}`).toString('base64');
+
+const headers = {
+  'Content-Type': 'application/json',
+  'Authorization': `Basic ${AUTH}`,
+};
+
+async function govFetch<T>(path: string, options: any = {}): Promise<T> {
+  const url = `${GOVERNANCE_URL}${path}`;
+  try {
+    const res = await fetch(url, { headers, ...options });
+    if (!res.ok) {
+      const text = await res.text();
+      throw new Error(`Governance API ${res.status}: ${text}`);
+    }
+    return await res.json() as T;
+  } catch (err: any) {
+    if (err.code === 'ECONNREFUSED') {
+      console.error(`[govClient] Governance API unavailable at ${GOVERNANCE_URL}`);
+      throw new Error('Governance API unavailable');
+    }
+    throw err;
+  }
+}
+
+// ─── Decisions ─────────────────────────────────
+
+export async function createDecision(
+  topic: string,
+  decisionType: DecisionType,
+  recommendation: string,
+  owner: string,
+  source: string = 'boardroom-v2'
+): Promise<GovDecision> {
+  return govFetch<GovDecision>('/api/decisions', {
+    method: 'POST',
+    body: JSON.stringify({ topic, decisionType, recommendation, owner, source }),
+  });
+}
+
+export async function approveDecision(id: string): Promise<GovDecision> {
+  return govFetch<GovDecision>(`/api/decisions/${id}/approve`, { method: 'POST' });
+}
+
+export async function rejectDecision(id: string, reason?: string): Promise<GovDecision> {
+  return govFetch<GovDecision>(`/api/decisions/${id}/reject`, {
+    method: 'POST',
+    body: JSON.stringify({ reason }),
+  });
+}
+
+export async function deferDecision(id: string): Promise<GovDecision> {
+  return govFetch<GovDecision>(`/api/decisions/${id}/defer`, { method: 'POST' });
+}
+
+export async function getDecisions(status?: string): Promise<GovDecision[]> {
+  const qs = status ? `?status=${status}` : '';
+  return govFetch<GovDecision[]>(`/api/decisions${qs}`);
+}
+
+export async function getDecisionStats(): Promise<GovDecisionStats> {
+  return govFetch<GovDecisionStats>('/api/decisions/stats');
+}
+
+// ─── Delegations ───────────────────────────────
+
+export async function createDelegation(
+  owner: string,
+  delegate: string,
+  autonomyLevel: number,
+  metric?: string
+): Promise<GovDelegation> {
+  return govFetch<GovDelegation>('/api/delegations', {
+    method: 'POST',
+    body: JSON.stringify({ owner, delegate, autonomyLevel, metric }),
+  });
+}
+
+export async function suggestDelegate(query: string): Promise<any> {
+  return govFetch<any>('/api/delegations/suggest', {
+    method: 'POST',
+    body: JSON.stringify({ query }),
+  });
+}
+
+export async function getDelegations(): Promise<GovDelegation[]> {
+  return govFetch<GovDelegation[]>('/api/delegations');
+}
+
+// ─── Escalations ───────────────────────────────
+
+export async function triggerEscalationCheck(): Promise<any> {
+  return govFetch<any>('/api/escalations/check', { method: 'POST' });
+}
+
+export async function getActiveEscalations(): Promise<GovEscalation[]> {
+  return govFetch<GovEscalation[]>('/api/escalations/active');
+}
+
+// ─── Governance Config ─────────────────────────
+
+export async function getGovernanceStatus(): Promise<GovStatus> {
+  return govFetch<GovStatus>('/api/governance/status');
+}
+
+export async function getGovernanceConfig(): Promise<Record<string, any>> {
+  return govFetch<Record<string, any>>('/api/governance/config');
+}
+
+// ─── Health Check ──────────────────────────────
+
+export async function isGovernanceAvailable(): Promise<boolean> {
+  try {
+    const res = await fetch(`${GOVERNANCE_URL}/health`);
+    return res.ok;
+  } catch {
+    return false;
+  }
+}
diff --git a/src/engine/meetingEngine.ts b/src/engine/meetingEngine.ts
new file mode 100644
index 0000000..410cfe8
--- /dev/null
+++ b/src/engine/meetingEngine.ts
@@ -0,0 +1,376 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Meeting Engine
+// 8-phase orchestrator with mutex and auto-advance
+// Ported from boardroom meeting-engine.js
+// ═══════════════════════════════════════════════
+
+import { v4 as uuid } from 'uuid';
+import * as db from '../db/sqlite';
+import { broadcast } from '../ws/broadcast';
+import { generateDialogue } from './agentDialogue';
+import { TEAM_AGENTS, EXECUTIVES, BOSS_PERSONA, getAgentsByDept, DEPARTMENT_GROUPS } from '../agents/registry';
+import {
+  MEETING_PHASES, PHASE_DURATIONS, PHASE_LABELS,
+  type MeetingPhase, type MeetingType, type Meeting, type MeetingMessage, type AgendaItem
+} from '../types';
+
+let activeMeeting: string | null = null;
+let phaseTimer: NodeJS.Timeout | null = null;
+let meetingMutex = false;
+
+// ─── Public API ────────────────────────────────
+
+export function getActiveMeeting(): Meeting | undefined {
+  if (!activeMeeting) return undefined;
+  return db.queryOne<Meeting>('SELECT * FROM br2_meetings WHERE id = ?', [activeMeeting]);
+}
+
+export function getMeeting(id: string): Meeting | undefined {
+  return db.queryOne<Meeting>('SELECT * FROM br2_meetings WHERE id = ?', [id]);
+}
+
+export function listMeetings(limit = 50): Meeting[] {
+  return db.query<Meeting>('SELECT * FROM br2_meetings ORDER BY created_at DESC LIMIT ?', [limit]);
+}
+
+export function getMeetingMessages(meetingId: string, limit = 500): MeetingMessage[] {
+  return db.query<MeetingMessage>(
+    'SELECT * FROM br2_messages WHERE meeting_id = ? ORDER BY created_at ASC LIMIT ?',
+    [meetingId, limit]
+  );
+}
+
+export function getAgendaItems(meetingId: string): AgendaItem[] {
+  return db.query<AgendaItem>(
+    'SELECT * FROM br2_agenda WHERE meeting_id = ? ORDER BY position ASC',
+    [meetingId]
+  );
+}
+
+export async function startMeeting(type: MeetingType): Promise<Meeting> {
+  if (meetingMutex) {
+    throw new Error('Meeting engine is busy');
+  }
+  if (activeMeeting) {
+    throw new Error(`Meeting ${activeMeeting} is already in progress`);
+  }
+
+  meetingMutex = true;
+  try {
+    const id = uuid();
+    const attendees = JSON.stringify([
+      ...EXECUTIVES.map(e => e.id),
+      ...TEAM_AGENTS.map(a => a.id),
+    ]);
+
+    db.run(
+      `INSERT INTO br2_meetings (id, meeting_type, phase, status, started_at, attendees)
+       VALUES (?, ?, 'caucus', 'in_progress', datetime('now'), ?)`,
+      [id, type, attendees]
+    );
+
+    activeMeeting = id;
+    const meeting = getMeeting(id)!;
+
+    // President Lincoln takes the podium
+    addMessage(id, 'lincoln', BOSS_PERSONA.name, `${type.charAt(0).toUpperCase() + type.slice(1)} meeting initiated. ${BOSS_PERSONA.name} takes the podium.`, 'system');
+
+    broadcast({ type: 'meeting_started', payload: meeting, timestamp: new Date().toISOString() });
+
+    // Start phase orchestration
+    await runPhase(id, 'caucus');
+
+    return meeting;
+  } finally {
+    meetingMutex = false;
+  }
+}
+
+export function haltMeeting(meetingId: string): Meeting {
+  const meeting = getMeeting(meetingId);
+  if (!meeting || meeting.status !== 'in_progress') {
+    throw new Error('No active meeting to halt');
+  }
+
+  clearPhaseTimer();
+  db.run(
+    `UPDATE br2_meetings SET status = 'halted', ended_at = datetime('now') WHERE id = ?`,
+    [meetingId]
+  );
+  activeMeeting = null;
+
+  addMessage(meetingId, 'system', 'Boardroom', 'Meeting halted by directive.', 'system');
+  const updated = getMeeting(meetingId)!;
+  broadcast({ type: 'meeting_halted', payload: updated, timestamp: new Date().toISOString() });
+  return updated;
+}
+
+export async function advancePhase(meetingId: string): Promise<Meeting> {
+  const meeting = getMeeting(meetingId);
+  if (!meeting || meeting.status !== 'in_progress') {
+    throw new Error('No active meeting to advance');
+  }
+
+  const currentIdx = MEETING_PHASES.indexOf(meeting.phase as MeetingPhase);
+  if (currentIdx >= MEETING_PHASES.length - 1) {
+    return completeMeeting(meetingId);
+  }
+
+  clearPhaseTimer();
+  const nextPhase = MEETING_PHASES[currentIdx + 1];
+  await runPhase(meetingId, nextPhase);
+  return getMeeting(meetingId)!;
+}
+
+export function addMessage(
+  meetingId: string,
+  agentId: string,
+  agentName: string,
+  message: string,
+  messageType: string = 'dialogue'
+): MeetingMessage {
+  const meeting = getMeeting(meetingId);
+  db.run(
+    `INSERT INTO br2_messages (meeting_id, agent_id, agent_name, message, message_type, phase)
+     VALUES (?, ?, ?, ?, ?, ?)`,
+    [meetingId, agentId, agentName, message, messageType, meeting?.phase || 'unknown']
+  );
+
+  const msg = db.queryOne<MeetingMessage>(
+    'SELECT * FROM br2_messages WHERE meeting_id = ? ORDER BY id DESC LIMIT 1',
+    [meetingId]
+  )!;
+
+  broadcast({ type: 'meeting_message', payload: msg, timestamp: new Date().toISOString() });
+  return msg;
+}
+
+// ─── Agenda CRUD ───────────────────────────────
+
+export function addAgendaItem(
+  meetingId: string,
+  topic: string,
+  recommendation?: string,
+  decisionType?: string,
+  owner?: string
+): AgendaItem {
+  const items = getAgendaItems(meetingId);
+  if (items.length >= 5) {
+    throw new Error('Maximum 5 agenda items per meeting');
+  }
+
+  db.run(
+    `INSERT INTO br2_agenda (meeting_id, topic, recommendation, decision_type, owner, position)
+     VALUES (?, ?, ?, ?, ?, ?)`,
+    [meetingId, topic, recommendation || null, decisionType || 'B', owner || null, items.length]
+  );
+
+  const item = db.queryOne<AgendaItem>(
+    'SELECT * FROM br2_agenda WHERE meeting_id = ? ORDER BY id DESC LIMIT 1',
+    [meetingId]
+  )!;
+
+  broadcast({ type: 'agenda_updated', payload: { meetingId, items: getAgendaItems(meetingId) }, timestamp: new Date().toISOString() });
+  return item;
+}
+
+export function updateAgendaItem(meetingId: string, itemId: number, updates: Partial<AgendaItem>): AgendaItem {
+  const sets: string[] = [];
+  const params: any[] = [];
+
+  if (updates.topic !== undefined) { sets.push('topic = ?'); params.push(updates.topic); }
+  if (updates.recommendation !== undefined) { sets.push('recommendation = ?'); params.push(updates.recommendation); }
+  if (updates.decision_type !== undefined) { sets.push('decision_type = ?'); params.push(updates.decision_type); }
+  if (updates.owner !== undefined) { sets.push('owner = ?'); params.push(updates.owner); }
+  if (updates.position !== undefined) { sets.push('position = ?'); params.push(updates.position); }
+  if (updates.status !== undefined) { sets.push('status = ?'); params.push(updates.status); }
+
+  if (sets.length > 0) {
+    params.push(itemId, meetingId);
+    db.run(`UPDATE br2_agenda SET ${sets.join(', ')} WHERE id = ? AND meeting_id = ?`, params);
+  }
+
+  return db.queryOne<AgendaItem>('SELECT * FROM br2_agenda WHERE id = ?', [itemId])!;
+}
+
+export function deleteAgendaItem(meetingId: string, itemId: number): void {
+  db.run('DELETE FROM br2_agenda WHERE id = ? AND meeting_id = ?', [itemId, meetingId]);
+}
+
+// ─── Phase Orchestration ───────────────────────
+
+async function runPhase(meetingId: string, phase: MeetingPhase): Promise<void> {
+  db.run('UPDATE br2_meetings SET phase = ? WHERE id = ?', [phase, meetingId]);
+
+  const label = PHASE_LABELS[phase];
+  addMessage(meetingId, 'system', 'Boardroom', `--- Phase: ${label} ---`, 'phase');
+  broadcast({
+    type: 'meeting_phase',
+    payload: { meetingId, phase, label },
+    timestamp: new Date().toISOString(),
+  });
+
+  // Run phase-specific logic
+  try {
+    await executePhaseLogic(meetingId, phase);
+  } catch (err: any) {
+    console.error(`[meetingEngine] Phase ${phase} error:`, err.message);
+    addMessage(meetingId, 'system', 'Boardroom', `Phase error: ${err.message}`, 'system');
+  }
+
+  // Schedule auto-advance
+  const duration = PHASE_DURATIONS[phase] * 1000;
+  phaseTimer = setTimeout(async () => {
+    try {
+      const meeting = getMeeting(meetingId);
+      if (meeting && meeting.status === 'in_progress') {
+        const idx = MEETING_PHASES.indexOf(phase);
+        if (idx < MEETING_PHASES.length - 1) {
+          await runPhase(meetingId, MEETING_PHASES[idx + 1]);
+        } else {
+          completeMeeting(meetingId);
+        }
+      }
+    } catch (err: any) {
+      console.error(`[meetingEngine] Auto-advance error:`, err.message);
+    }
+  }, duration);
+}
+
+async function executePhaseLogic(meetingId: string, phase: MeetingPhase): Promise<void> {
+  const meeting = getMeeting(meetingId)!;
+  const type = meeting.meeting_type;
+  const agenda = getAgendaItems(meetingId);
+
+  switch (phase) {
+    case 'caucus': {
+      // President Lincoln opens from the podium
+      const agentCount = TEAM_AGENTS.length;
+      const execCount = EXECUTIVES.length;
+      addMessage(meetingId, 'lincoln', BOSS_PERSONA.name, `Good day. This ${type} session is now convened with ${agentCount} agents and ${execCount} executives present. Let us proceed with focus and purpose.`, 'dialogue');
+      // CEO acknowledges
+      addMessage(meetingId, 'ceo', 'Curtis', `Thank you, ${BOSS_PERSONA.title}. All departments are standing by.`, 'dialogue');
+      break;
+    }
+
+    case 'call_to_order': {
+      // Roll call — pick 3-5 key agents based on meeting type
+      const keyAgents = getKeyAgentsForType(type);
+      for (const agent of keyAgents.slice(0, 3)) {
+        const dialogue = await generateDialogue(agent.id, agent.name, agent.role, phase, `Acknowledge presence at ${type} meeting`, type);
+        addMessage(meetingId, agent.id, agent.name, dialogue, 'dialogue');
+      }
+      break;
+    }
+
+    case 'old_business': {
+      // Review previous action items — COO reports
+      const dialogue = await generateDialogue('coo', 'Conrad', 'Chief Operations Officer', phase, 'Report on outstanding action items from previous meeting', type);
+      addMessage(meetingId, 'coo', 'Conrad', dialogue, 'dialogue');
+      break;
+    }
+
+    case 'current_business': {
+      // Discuss agenda items
+      if (agenda.length === 0) {
+        addMessage(meetingId, 'system', 'Boardroom', 'No agenda items to discuss.', 'system');
+      } else {
+        for (const item of agenda.slice(0, 3)) {
+          const owner = item.owner || 'ceo';
+          const ownerAgent = TEAM_AGENTS.find(a => a.id === owner) || EXECUTIVES.find(e => e.id === owner);
+          const name = ownerAgent ? ('name' in ownerAgent ? ownerAgent.name : owner) : owner;
+          const role = ownerAgent ? ('role' in ownerAgent ? ownerAgent.role : '') : '';
+
+          const dialogue = await generateDialogue(owner, name, role, phase, `Present agenda item: ${item.topic}. Recommendation: ${item.recommendation || 'None'}`, type);
+          addMessage(meetingId, owner, name, dialogue, 'dialogue');
+
+          updateAgendaItem(meetingId, item.id, { status: 'discussed' });
+        }
+      }
+      break;
+    }
+
+    case 'new_business': {
+      // Remaining agenda items + department heads report
+      const remaining = agenda.filter(a => a.status === 'pending');
+      for (const item of remaining.slice(0, 2)) {
+        const owner = item.owner || 'vp-ops';
+        const ownerAgent = TEAM_AGENTS.find(a => a.id === owner) || EXECUTIVES.find(e => e.id === owner);
+        const name = ownerAgent ? ('name' in ownerAgent ? ownerAgent.name : owner) : owner;
+        const role = ownerAgent ? ('role' in ownerAgent ? ownerAgent.role : '') : '';
+
+        const dialogue = await generateDialogue(owner, name, role, phase, `Raise new business: ${item.topic}`, type);
+        addMessage(meetingId, owner, name, dialogue, 'dialogue');
+        updateAgendaItem(meetingId, item.id, { status: 'discussed' });
+      }
+      break;
+    }
+
+    case 'breakout': {
+      // Brief departmental breakout summaries
+      const deptLeaders = ['max', 'grant', 'shane', 'atlas'];
+      for (const leaderId of deptLeaders.slice(0, 2)) {
+        const agent = TEAM_AGENTS.find(a => a.id === leaderId);
+        if (agent) {
+          const dialogue = await generateDialogue(agent.id, agent.name, agent.role, phase, `Brief departmental summary for ${agent.dept}`, type);
+          addMessage(meetingId, agent.id, agent.name, dialogue, 'dialogue');
+        }
+      }
+      break;
+    }
+
+    case 'minutes': {
+      // Note taker summarizes
+      const dialogue = await generateDialogue('nate', 'Nate', 'Note Taker & Records', phase, `Summarize key points and action items from this ${type} meeting`, type);
+      addMessage(meetingId, 'nate', 'Nate', dialogue, 'dialogue');
+      break;
+    }
+
+    case 'adjournment': {
+      // CEO delivers closing remarks to President Lincoln
+      const dialogue = await generateDialogue('ceo', 'Curtis', 'Chief Executive', phase, `Deliver closing remarks to President Lincoln for this ${type} meeting. Address him as Mr. President.`, type);
+      addMessage(meetingId, 'ceo', 'Curtis', dialogue, 'dialogue');
+      // President Lincoln adjourns
+      addMessage(meetingId, 'lincoln', BOSS_PERSONA.name, `This ${type} session stands adjourned. Thank you all for your contributions. Dismissed.`, 'dialogue');
+      break;
+    }
+  }
+}
+
+function completeMeeting(meetingId: string): Meeting {
+  clearPhaseTimer();
+  db.run(
+    `UPDATE br2_meetings SET status = 'completed', ended_at = datetime('now'), phase = 'adjournment' WHERE id = ?`,
+    [meetingId]
+  );
+  activeMeeting = null;
+
+  addMessage(meetingId, 'system', 'Boardroom', 'Meeting adjourned.', 'system');
+  const meeting = getMeeting(meetingId)!;
+  broadcast({ type: 'meeting_completed', payload: meeting, timestamp: new Date().toISOString() });
+  return meeting;
+}
+
+function clearPhaseTimer(): void {
+  if (phaseTimer) {
+    clearTimeout(phaseTimer);
+    phaseTimer = null;
+  }
+}
+
+function getKeyAgentsForType(type: string): { id: string; name: string; role: string }[] {
+  const all = [...EXECUTIVES.map(e => ({ id: e.id, name: e.name, role: e.role }))];
+
+  switch (type) {
+    case 'strategic':
+      return [...all, { id: 'frank', name: 'Lance', role: 'Legal Compliance' }, { id: 'grant', name: 'Finn', role: 'Finance' }];
+    case 'initiative':
+      return [...all, { id: 'atlas', name: 'Porter', role: 'Product Master' }, { id: 'bob', name: 'Craig', role: 'Content Creator' }];
+    case 'ops':
+      return [...all, { id: 'max', name: 'Ford', role: 'Fleet Commander' }, { id: 'hawk', name: 'Sven', role: 'System Monitor' }];
+    case 'emergency':
+      return [...all, { id: 'max', name: 'Ford', role: 'Fleet Commander' }, { id: 'vince', name: 'Vince', role: 'Site Monitor' }];
+    default:
+      return all;
+  }
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..57e4941
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,182 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — Core Types
+// ═══════════════════════════════════════════════
+
+// Meeting phases — ported from boardroom meeting-engine.js
+export const MEETING_PHASES = [
+  'caucus', 'call_to_order', 'old_business', 'current_business',
+  'new_business', 'breakout', 'minutes', 'adjournment'
+] as const;
+export type MeetingPhase = typeof MEETING_PHASES[number];
+
+// Phase durations in seconds
+export const PHASE_DURATIONS: Record<MeetingPhase, number> = {
+  caucus: 120,
+  call_to_order: 60,
+  old_business: 180,
+  current_business: 300,
+  new_business: 300,
+  breakout: 180,
+  minutes: 120,
+  adjournment: 60,
+};
+
+export const PHASE_LABELS: Record<MeetingPhase, string> = {
+  caucus: 'Caucus',
+  call_to_order: 'Call to Order',
+  old_business: 'Old Business',
+  current_business: 'Current Business',
+  new_business: 'New Business',
+  breakout: 'Breakout',
+  minutes: 'Minutes',
+  adjournment: 'Adjournment',
+};
+
+export type MeetingType = 'strategic' | 'initiative' | 'ops' | 'emergency';
+export type MeetingStatus = 'scheduled' | 'in_progress' | 'completed' | 'halted' | 'crashed';
+export type MessageType = 'dialogue' | 'phase' | 'system' | 'action' | 'decision';
+export type DecisionType = 'A' | 'B' | 'C';
+export type AgendaItemStatus = 'pending' | 'discussed' | 'resolved' | 'deferred';
+
+// ─── Models ────────────────────────────────────
+
+export interface Meeting {
+  id: string;
+  meeting_type: MeetingType;
+  phase: MeetingPhase;
+  status: MeetingStatus;
+  started_at: string | null;
+  ended_at: string | null;
+  summary: string | null;
+  attendees: string;   // JSON array
+  action_items: string; // JSON array
+  created_at: string;
+}
+
+export interface MeetingMessage {
+  id: number;
+  meeting_id: string;
+  agent_id: string;
+  agent_name: string;
+  message: string;
+  message_type: MessageType;
+  phase: string | null;
+  created_at: string;
+}
+
+export interface AgendaItem {
+  id: number;
+  meeting_id: string;
+  topic: string;
+  recommendation: string | null;
+  decision_type: DecisionType;
+  owner: string | null;
+  position: number;
+  status: AgendaItemStatus;
+  created_at: string;
+}
+
+// ─── Agent System ──────────────────────────────
+
+export interface Agent {
+  id: string;
+  name: string;
+  pm2: string;
+  port?: number;
+  color: string;
+  role: string;
+  dept: string;
+  hasApi: boolean;
+  subMgr?: string;
+  isNoteTaker?: boolean;
+}
+
+export interface Executive {
+  id: string;
+  name: string;
+  port: number;
+  color: string;
+  role: string;
+  avatar: string;
+}
+
+export interface DeptGroup {
+  emoji: string;
+  color: string;
+  leader: string;
+  exec: string;
+  label: string;
+}
+
+// ─── Voice ─────────────────────────────────────
+
+export interface VoiceConfig {
+  tts_enabled: boolean;
+  tts_speed: number;
+  tts_volume: number;
+  auto_speak: boolean;
+}
+
+// ─── Governance Client Types ───────────────────
+
+export interface GovDecision {
+  id: string;
+  topic: string;
+  decision_type: DecisionType;
+  recommendation: string | null;
+  owner: string;
+  status: string;
+  score: number | null;
+  created_at: string;
+}
+
+export interface GovDelegation {
+  id: string;
+  owner: string;
+  delegate: string;
+  autonomy_level: number;
+  status: string;
+}
+
+export interface GovEscalation {
+  id: number;
+  decision_id: string | null;
+  escalation_type: string;
+  hours_elapsed: number;
+  action_taken: string | null;
+}
+
+export interface GovStatus {
+  governance_mode: string;
+  active_meeting: any;
+  pending_decisions: number;
+  active_delegations: number;
+  active_escalations: number;
+}
+
+export interface GovDecisionStats {
+  total: number;
+  pending: number;
+  approved: number;
+  rejected: number;
+  deferred: number;
+}
+
+// ─── WebSocket Events ──────────────────────────
+
+export type WSEventType =
+  | 'connected'
+  | 'meeting_started'
+  | 'meeting_phase'
+  | 'meeting_halted'
+  | 'meeting_completed'
+  | 'meeting_message'
+  | 'agent_speaking'
+  | 'agenda_updated'
+  | 'governance_event';
+
+export interface WSEvent {
+  type: WSEventType;
+  payload: any;
+  timestamp: string;
+}
diff --git a/src/ws/broadcast.ts b/src/ws/broadcast.ts
new file mode 100644
index 0000000..60c94e7
--- /dev/null
+++ b/src/ws/broadcast.ts
@@ -0,0 +1,62 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom V2 — WebSocket Broadcast
+// Real-time meeting events to frontend + War Room
+// ═══════════════════════════════════════════════
+
+import { WebSocketServer, WebSocket } from 'ws';
+import type { Server } from 'http';
+import type { WSEvent } from '../types';
+
+let wss: WebSocketServer;
+
+export function initWebSocket(server: Server): WebSocketServer {
+  wss = new WebSocketServer({ server, path: '/ws' });
+
+  wss.on('connection', (ws) => {
+    console.log(`[ws] Client connected (${getConnectionCount()} total)`);
+
+    ws.send(JSON.stringify({
+      type: 'connected',
+      payload: { message: 'Connected to Boardroom V2 WebSocket' },
+      timestamp: new Date().toISOString(),
+    }));
+
+    ws.on('close', () => {
+      console.log(`[ws] Client disconnected (${getConnectionCount() - 1} remaining)`);
+    });
+
+    ws.on('error', (err) => {
+      console.error('[ws] Error:', err.message);
+    });
+  });
+
+  console.log(`[ws] WebSocket server initialized on /ws`);
+  return wss;
+}
+
+export function broadcast(event: WSEvent): void {
+  if (!wss) return;
+
+  const data = JSON.stringify(event);
+  let sent = 0;
+
+  wss.clients.forEach((client) => {
+    if (client.readyState === WebSocket.OPEN) {
+      client.send(data);
+      sent++;
+    }
+  });
+
+  if (sent > 0) {
+    console.log(`[ws] Broadcast ${event.type} to ${sent} clients`);
+  }
+}
+
+export function getConnectionCount(): number {
+  if (!wss) return 0;
+  let count = 0;
+  wss.clients.forEach((client) => {
+    if (client.readyState === WebSocket.OPEN) count++;
+  });
+  return count;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3e1f375
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "module": "commonjs",
+    "lib": ["ES2020"],
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "resolveJsonModule": true,
+    "declaration": true,
+    "declarationMap": true,
+    "sourceMap": true
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist", "frontend"]
+}

(oldest)  ·  back to Dw Boardroom V2  ·  Stop tracking SQLite WAL/shm runtime files; gitignore *.db-s 2260e48 →