diff --git a/.github/Repo.toml b/.github/Repo.toml new file mode 100644 index 0000000..654a0e0 --- /dev/null +++ b/.github/Repo.toml @@ -0,0 +1,4 @@ +[scopes] +config = ["wrangler.toml", "package.json", ".gitignore"] +docs = ["README.md"] +core = ["src"] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fe43a63 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,178 @@ +# 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 `