diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..fdced1b
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+YOUTUBE_LIVE_ID=""
+INNERTUBE_API_KEY="" # optional override
+INNERTUBE_CLIENT_NAME="" # optional override
+INNERTUBE_CLIENT_VERSION="" # optional override
+SESSION_SECRET="change-me"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cd0f817
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules
+pnpm-lock.yaml
+.env.local
+.env
+.DS_Store
+.next
+out
+dist
+coverage
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..c7958ee
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,121 @@
+# Cline's Memory Bank
+
+I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional.
+
+## Memory Bank Structure
+
+The Memory Bank consists of core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy:
+
+flowchart TD
+PB[projectbrief.md] --> PC[productContext.md]
+PB --> SP[systemPatterns.md]
+PB --> TC[techContext.md]
+
+ PC --> AC[activeContext.md]
+ SP --> AC
+ TC --> AC
+
+ AC --> P[progress.md]
+
+### Core Files (Required)
+
+1. `projectbrief.md`
+ - Foundation document that shapes all other files
+ - Created at project start if it doesn't exist
+ - Defines core requirements and goals
+ - Source of truth for project scope
+
+2. `productContext.md`
+ - Why this project exists
+ - Problems it solves
+ - How it should work
+ - User experience goals
+
+3. `activeContext.md`
+ - Current work focus
+ - Recent changes
+ - Next steps
+ - Active decisions and considerations
+ - Important patterns and preferences
+ - Learnings and project insights
+
+4. `systemPatterns.md`
+ - System architecture
+ - Key technical decisions
+ - Design patterns in use
+ - Component relationships
+ - Critical implementation paths
+
+5. `techContext.md`
+ - Technologies used
+ - Development setup
+ - Technical constraints
+ - Dependencies
+ - Tool usage patterns
+
+6. `progress.md`
+ - What works
+ - What's left to build
+ - Current status
+ - Known issues
+ - Evolution of project decisions
+
+### Additional Context
+
+Create additional files/folders within memory-bank/ when they help organize:
+
+- Complex feature documentation
+- Integration specifications
+- API documentation
+- Testing strategies
+- Deployment procedures
+
+## Core Workflows
+
+### Plan Mode
+
+flowchart TD
+Start[Start] --> ReadFiles[Read Memory Bank]
+ReadFiles --> CheckFiles{Files Complete?}
+
+ CheckFiles -->|No| Plan[Create Plan]
+ Plan --> Document[Document in Chat]
+
+ CheckFiles -->|Yes| Verify[Verify Context]
+ Verify --> Strategy[Develop Strategy]
+ Strategy --> Present[Present Approach]
+
+### Act Mode
+
+flowchart TD
+Start[Start] --> Context[Check Memory Bank]
+Context --> Update[Update Documentation]
+Update --> Execute[Execute Task]
+Execute --> Document[Document Changes]
+
+## Documentation Updates
+
+Memory Bank updates occur when:
+
+1. Discovering new project patterns
+2. After implementing significant changes
+3. When user requests with **update memory bank** (MUST review ALL files)
+4. When context needs clarification
+
+flowchart TD
+Start[Update Process]
+
+ subgraph Process
+ P1[Review ALL Files]
+ P2[Document Current State]
+ P3[Clarify Next Steps]
+ P4[Document Insights & Patterns]
+
+ P1 --> P2 --> P3 --> P4
+ end
+
+ Start --> Process
+
+Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state.
+
+REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy.
diff --git a/apps/client/app/globals.css b/apps/client/app/globals.css
new file mode 100644
index 0000000..ae2b4fb
--- /dev/null
+++ b/apps/client/app/globals.css
@@ -0,0 +1,10 @@
+:root {
+ color-scheme: dark light;
+}
+
+body {
+ margin: 0;
+ font-family: system-ui, sans-serif;
+ background: #0f0f0f;
+ color: #f5f5f5;
+}
diff --git a/apps/client/app/layout.tsx b/apps/client/app/layout.tsx
new file mode 100644
index 0000000..4e30728
--- /dev/null
+++ b/apps/client/app/layout.tsx
@@ -0,0 +1,15 @@
+import './globals.css';
+import type { ReactNode } from 'react';
+
+export const metadata = {
+ title: 'YouTube Chat Client',
+ description: 'High-performance YouTube Live chat dashboard'
+};
+
+export default function RootLayout({ children }: { children: ReactNode }) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/apps/client/app/page.tsx b/apps/client/app/page.tsx
new file mode 100644
index 0000000..41b76a1
--- /dev/null
+++ b/apps/client/app/page.tsx
@@ -0,0 +1,8 @@
+export default function HomePage() {
+ return (
+
+ youtube-client
+ Operator dashboard under construction.
+
+ );
+}
diff --git a/apps/client/next-env.d.ts b/apps/client/next-env.d.ts
new file mode 100644
index 0000000..c6643fd
--- /dev/null
+++ b/apps/client/next-env.d.ts
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/apps/client/next.config.js b/apps/client/next.config.js
new file mode 100644
index 0000000..420bdd2
--- /dev/null
+++ b/apps/client/next.config.js
@@ -0,0 +1,9 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+ experimental: {
+ serverActions: true
+ }
+};
+
+module.exports = nextConfig;
diff --git a/apps/client/package.json b/apps/client/package.json
new file mode 100644
index 0000000..6c62110
--- /dev/null
+++ b/apps/client/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "client",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Operator dashboard and overlay UI.",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ }
+}
diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json
new file mode 100644
index 0000000..dbb5dad
--- /dev/null
+++ b/apps/client/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "types": ["next", "next/types/global", "next/image-types/global"],
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "noEmit": true
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md
new file mode 100644
index 0000000..30660e0
--- /dev/null
+++ b/memory-bank/activeContext.md
@@ -0,0 +1,19 @@
+# Active Context
+
+## Current Focus
+- Maintain Memory Bank documentation and scaffold the monorepo structure for the YouTube Live chat client.
+- Define onboarding flow for Innertube-based chat ingestion and configuration.
+
+## Recent Decisions
+- Switch from official YouTube Data API to Innertube (`youtubei.js`) ingestion to avoid quota issues.
+- Use Next.js for operator UI and OBS overlay, with a separate backend worker for polling and realtime events.
+- Prefer Server-Sent Events for one-way overlay updates; keep WebSocket option in mind for future enhancements.
+
+## Immediate Next Steps
+1. Initialize `pnpm` workspace with `apps/client`, `packages/backend`, and `packages/shared` directories. (Scaffolded.)
+2. Configure baseline project files: `package.json`, `pnpm-workspace.yaml`, TS configs, linting setup. (Scaffolded.)
+3. Stub backend poller using `youtubei.js` to verify dev scripts once dependencies are installed.
+
+## Open Questions
+- How to persist or refresh Innertube context data (visitor data, API key) between sessions for reliability.
+- Whether to include optional SQLite persistence from the outset or add once basic flow is working.
diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md
new file mode 100644
index 0000000..7484afc
--- /dev/null
+++ b/memory-bank/productContext.md
@@ -0,0 +1,20 @@
+# Product Context
+
+## Why This Project Exists
+Live streamers rely on YouTube’s default chat panel, which can feel sluggish, cluttered, and unreliable during high-traffic moments. Streamers also need a smoother workflow to feature chat messages inside OBS without manual copy/paste or third-party widgets.
+
+## Target Users
+- Primary: Single YouTube streamer operating their own broadcast setup.
+- Secondary: Community contributors who want to customize or extend the client for similar use cases.
+
+## User Goals
+- Launch the app locally and connect to their live chat with minimal configuration.
+- View chat in a fast, filterable interface that highlights new messages clearly.
+- Select a message to instantly display in an overlay browser source within OBS.
+- Trust that the app will keep running throughout long streams without desync or crashes.
+
+## Experience Principles
+- **Performance First:** Avoid lag by batching updates, minimizing re-renders, and offloading work to background processes.
+- **Operational Clarity:** Provide clear status indicators for connection health, rate limits, and overlay sync.
+- **Low Friction:** Onboarding should consist of OAuth login once and a simple start command.
+- **Extensible:** Keep architecture modular so advanced users can add features (moderation tools, multi-stream support) later.
diff --git a/memory-bank/progress.md b/memory-bank/progress.md
new file mode 100644
index 0000000..645ca42
--- /dev/null
+++ b/memory-bank/progress.md
@@ -0,0 +1,29 @@
+# Progress Tracker
+
+## Phase 0 – Foundations
+- [x] Create Memory Bank documentation.
+- [x] Scaffold pnpm workspace structure.
+- [ ] Commit baseline configs and ensure dev scripts run.
+
+## Phase 1 – Core Infrastructure
+- [ ] Implement Innertube client bootstrap (retrieve context, manage continuation tokens).
+- [ ] Build backend poller with message normalization and rate/error handling.
+- [ ] Expose REST+SSE endpoints for chat and overlay delivery.
+
+## Phase 2 – Operator Dashboard
+- [ ] Implement chat feed UI with filters/search and live updates.
+- [ ] Provide message selection controls and status indicators.
+- [ ] Handle error states (rate limits, disconnects) gracefully in UI.
+
+## Phase 3 – OBS Overlay Experience
+- [ ] Create minimal overlay page that consumes SSE stream.
+- [ ] Style overlay for readability and ensure quick updates in OBS browser source.
+- [ ] Add local preview within dashboard for operator verification.
+
+## Phase 4 – Reliability & Polish
+- [ ] Add optional persistence (SQLite) and crash recovery.
+- [ ] Expand logging/metrics for long-stream observability.
+- [ ] Write tests (unit/integration) and contributor documentation.
+
+## Current Status
+- Memory Bank established; repository scaffolding in place; backend ingestion stubs pending.
diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md
new file mode 100644
index 0000000..4b5f125
--- /dev/null
+++ b/memory-bank/projectbrief.md
@@ -0,0 +1,21 @@
+# Project Brief
+
+## Vision
+Build a desktop-operated, open-source YouTube Live chat client that delivers a fast, reliable stream of messages and allows a streamer to spotlight a selected chat message on an OBS-ready overlay.
+
+## Core Requirements
+- Mirror chat from a single YouTube Live stream with low latency and high stability.
+- Provide an operator dashboard to browse, search, and filter live chat messages.
+- Enable one-click selection of a message and broadcast it to an overlay page consumable in OBS.
+- Keep setup approachable: clone repo, configure environment variables/OAuth, and run locally.
+- Prioritize performance and resilience over advanced user management or multi-stream support.
+
+## Constraints
+- Runs on the streamer’s personal computer; no external hosting assumed.
+- No end-user authentication beyond stored YouTube OAuth credentials.
+- Must tolerate YouTube API rate limits and intermittent network issues without crashing.
+
+## Success Criteria
+- Operator dashboard stays responsive during long streams (>4 hours) without memory leaks.
+- Overlay updates within ~1 second of operator selection.
+- Project documentation enables others to reproduce setup from scratch.
diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md
new file mode 100644
index 0000000..5098f91
--- /dev/null
+++ b/memory-bank/systemPatterns.md
@@ -0,0 +1,18 @@
+# System Patterns
+
+## Architecture Overview
+- **Monorepo Layout:** `pnpm` workspaces with `apps/client` (Next.js dashboard + overlay), `packages/backend` (Node ingestion + realtime gateway), `packages/shared` (types, schemas).
+- **Data Flow:**
+ 1. Backend worker polls YouTube Live chat through the Innertube (youtubei) API, maintaining continuation tokens.
+ 2. Messages stored in-memory (and optionally SQLite) and emitted over an internal event bus.
+ 3. Client dashboard fetches chat via HTTP (React Query) and pushes selection back via REST.
+ 4. Overlay page listens to Server-Sent Events stream for the currently highlighted message.
+- **Realtime Delivery:** SSE chosen for one-directional updates to OBS browser source; can swap to WebSocket if bidirectional control is required later.
+- **Configuration:** Environment variables drive stream IDs and optional auth tokens; local secrets persisted in `.env.local` or config files.
+
+## Key Patterns & Practices
+- Abstract ingestion behind an interface so alternate providers (official API, headless browser) can be swapped in quickly.
+- Cache Innertube visitor data and API keys locally to reduce startup latency and handle rotations gracefully.
+- Use Zod schemas in shared package to validate external responses and internal payloads.
+- Centralized error reporting/logging with structured logs for monitoring during streams.
+- Graceful degradation: exponential backoff on fetch failures, last-known overlay message cached to disk to survive restarts.
diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md
new file mode 100644
index 0000000..573b501
--- /dev/null
+++ b/memory-bank/techContext.md
@@ -0,0 +1,17 @@
+# Tech Context
+
+## Primary Stack
+- **Frontend:** Next.js 14 (App Router) + React 18 + TypeScript, styled with Tailwind CSS and optional shadcn/ui components.
+- **Backend Worker:** Node.js (Fastify) with `youtubei.js` for Innertube chat ingestion, `better-sqlite3` for persistence, EventEmitter for internal pub/sub.
+- **Realtime:** Server-Sent Events for overlay updates; potential future WebSocket support via `ws`.
+- **Tooling:** `pnpm` for workspace management, ESLint + Prettier, Zod for schema validation, Vitest/Playwright for testing (to be introduced later).
+
+## Environment & Dependencies
+- No official API quota required; ingestion relies on Innertube visitor tokens produced at runtime.
+- Required env values: `YOUTUBE_LIVE_ID` (or stream URL), optional overrides for Innertube API key/context if we need to pin versions.
+- Local `.env.local` file manages configuration; sample `.env.example` committed for contributors.
+
+## Constraints & Considerations
+- Innertube endpoints change occasionally; design ingestion to update keys dynamically and fall back to alternate strategies if responses shift.
+- Application expected to run on Windows/macOS/Linux desktops used for streaming; keep dependencies cross-platform and avoid native build steps when possible.
+- No external database by default; design backend to operate fully in-process with optional local persistence.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a95738e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "youtube-client",
+ "version": "0.1.0",
+ "private": true,
+ "description": "High-performance YouTube Live chat client for streamers.",
+ "license": "MIT",
+ "packageManager": "pnpm@8.15.4",
+ "scripts": {
+ "dev": "pnpm -r dev",
+ "build": "pnpm -r build",
+ "lint": "pnpm -r lint",
+ "start": "pnpm -r start"
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
new file mode 100644
index 0000000..3176aeb
--- /dev/null
+++ b/packages/backend/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "backend",
+ "version": "0.1.0",
+ "private": true,
+ "description": "YouTube chat poller and realtime bridge.",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "dev": "ts-node src/index.ts",
+ "build": "tsc -p tsconfig.build.json",
+ "start": "node dist/index.js",
+ "lint": "eslint 'src/**/*.{ts,tsx}'"
+ }
+}
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
new file mode 100644
index 0000000..c40b165
--- /dev/null
+++ b/packages/backend/src/index.ts
@@ -0,0 +1,12 @@
+import { bootstrapInnertube, fetchChatBatch } from './ingestion/youtubei';
+
+export async function startBackend() {
+ console.log('Starting backend worker (youtubei ingestion pending).');
+ const state = await bootstrapInnertube();
+ console.log('Initial continuation state', state);
+ await fetchChatBatch(state);
+}
+
+if (require.main === module) {
+ void startBackend();
+}
diff --git a/packages/backend/src/ingestion/youtubei.ts b/packages/backend/src/ingestion/youtubei.ts
new file mode 100644
index 0000000..9726e8d
--- /dev/null
+++ b/packages/backend/src/ingestion/youtubei.ts
@@ -0,0 +1,18 @@
+export type ContinuationState = {
+ token: string | null;
+ apiKey?: string;
+};
+
+export async function bootstrapInnertube(): Promise {
+ // TODO: fetch initial visitor data and live chat continuation token via youtubei.js
+ return { token: null };
+}
+
+export async function fetchChatBatch(state: ContinuationState) {
+ // TODO: use youtubei.js to fetch messages with the provided continuation token
+ // return both normalized messages and the next continuation token
+ return {
+ messages: [],
+ next: state.token
+ };
+}
diff --git a/packages/backend/tsconfig.build.json b/packages/backend/tsconfig.build.json
new file mode 100644
index 0000000..da48075
--- /dev/null
+++ b/packages/backend/tsconfig.build.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "emitDeclarationOnly": false
+ }
+}
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
new file mode 100644
index 0000000..ef1e1ea
--- /dev/null
+++ b/packages/backend/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "module": "CommonJS",
+ "target": "ES2021",
+ "noEmit": false
+ },
+ "include": ["src"]
+}
diff --git a/packages/shared/package.json b/packages/shared/package.json
new file mode 100644
index 0000000..aadfdf9
--- /dev/null
+++ b/packages/shared/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "shared",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Shared types and schemas.",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "dev": "tsc --watch -p tsconfig.json",
+ "build": "tsc -p tsconfig.json",
+ "start": "node dist/index.js",
+ "lint": "eslint 'src/**/*.{ts,tsx}'"
+ }
+}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
new file mode 100644
index 0000000..462e9b2
--- /dev/null
+++ b/packages/shared/src/index.ts
@@ -0,0 +1,6 @@
+export type ChatMessage = {
+ id: string;
+ author: string;
+ text: string;
+ publishedAt: string;
+};
diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json
new file mode 100644
index 0000000..e583ee8
--- /dev/null
+++ b/packages/shared/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "module": "ESNext",
+ "target": "ES2021",
+ "declaration": true,
+ "declarationMap": true,
+ "noEmit": false
+ },
+ "include": ["src"]
+}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..e9b0dad
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - 'apps/*'
+ - 'packages/*'
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..b9e83d2
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2021",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowJs": false,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "types": ["node"],
+ "baseUrl": "."
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..ebf0d3e
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "files": [],
+ "references": [
+ { "path": "apps/client" },
+ { "path": "packages/backend" },
+ { "path": "packages/shared" }
+ ]
+}