# AGENTS.md - Interview Bot > AI-powered interviewer bot with video call interface and calendar scheduling on Cloudflare Workers. ## Quick Reference | Command | Description | |---------|-------------| | `npm run dev` | Start local dev server (port 8787) | | `npm run deploy` | Deploy to Cloudflare Workers | | `npm run tail` | Stream production logs | ## Project Structure ``` src/index.ts # Entire app: API routes, AI chat, HTML/CSS/JS frontend wrangler.toml # Cloudflare Workers config (KV bindings, AI binding) package.json # Dependencies and npm scripts ``` **Single-file architecture**: All backend routes + frontend UI lives in `src/index.ts`. ## Build & Development ```bash npm install && npm run dev # Local dev at http://localhost:8787 npm run deploy # Deploy to production ``` ### KV Setup (First-time only) ```bash npx wrangler kv namespace create INTERVIEWS npx wrangler kv namespace create INTERVIEWS --preview # Update wrangler.toml with returned IDs ``` ## Testing **No test framework.** Manual testing only: - `GET /api/interviews` - List interviews - `POST /api/interviews` - Create interview - `PUT /api/interviews/:id` - Update interview - `DELETE /api/interviews/:id` - Delete interview - `POST /api/chat` - Stream AI chat ## Code Style ### TypeScript Patterns - **Runtime**: Cloudflare Workers (no Node.js APIs) - **No tsconfig.json**: Wrangler handles TypeScript compilation - **Explicit types**: Always type function params and return values - **Interfaces**: Define at file top, before constants ```typescript // Interfaces at top interface Interview { id: string; candidateName: string; status: 'scheduled' | 'in-progress' | 'completed' | 'cancelled'; } // Explicit return types async function getInterviews(env: Env, corsHeaders: Record): Promise { // ... } // Type assertions for JSON const body = await request.json() as Partial; ``` ### Naming Conventions | Element | Convention | Example | |---------|------------|---------| | Interfaces | PascalCase | `Interview`, `ChatMessage`, `Env` | | Functions | camelCase | `handleChat`, `getInterviews` | | Constants | SCREAMING_SNAKE | `SYSTEM_PROMPT` | | Variables | camelCase | `corsHeaders`, `currentView` | ### Error Handling ```typescript try { // operation } catch (error) { console.error('Error:', error); return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json', ...corsHeaders }, }); } ``` ### API Response Pattern ```typescript // Always include CORS headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; // JSON response return new Response(JSON.stringify(data), { status: 200, headers: { 'Content-Type': 'application/json', ...corsHeaders }, }); // SSE streaming response return new Response(stream as ReadableStream, { headers: { 'Content-Type': 'text/event-stream', ...corsHeaders }, }); ``` ### Routing (No framework) ```typescript if (path === '/api/interviews' && request.method === 'GET') { return await getInterviews(env, corsHeaders); } if (path.startsWith('/api/interviews/') && request.method === 'DELETE') { const id = path.split('/')[3]; return await deleteInterview(id, env, corsHeaders); } ``` ### Frontend (Inline in getHTML()) - CSS: Custom properties in `:root`, inline `