# Veridian AGENTS.md **Nuxt 4 AI chat platform with Drizzle + PostgreSQL + better-auth** ## Architecture Overview ``` app/ # Nuxt 4 application (SSR) ├── components/ # Vue components (Sidenav, Message, Settings, etc.) ├── composables/ # Shared state (useChat, useAuth, useModels, etc.) ├── layouts/ # Auth and default layouts ├── middleware/ # Global auth middleware ├── pages/ # File-based routing (/, /auth/*, /agent/*) ├── plugins/ # Auth plugins (client/server), remark markdown ├── types/ # TypeScript interfaces └── utils/ # Crypto, model-mapping, search utilities server/ # Server-side API routes └── api/ ├── auth/ # Better-auth handler ├── chat/ # Generation, cancel endpoints └── provider/ # Model fetching from providers drizzle/ # Database (migrated from Triplit) ├── schema.ts # Full schema with auth + app tables ├── relations.ts # Table relationships └── migrations/ # SQL migration files lib/ # Shared utilities ├── auth.ts # Better-auth server config └── auth-client.ts # Better-auth client ``` ## Core Commands As an agentic agent, never run the development server yourself. Instead, once you have completed your task, end generation and inform me that you are done and request a review of your changes. You are allowed to use database commands. ### Database Commands - `bunx drizzle-kit push` - Push schema changes to database - `bunx drizzle-kit generate` - Generate migration from schema changes - `bunx drizzle-kit migrate` - Run pending migrations ## Code Style - **Indentation**: 4 spaces (no tabs) - **Semicolons**: Always required - **Equality**: Strict equality only (`===`, `!==`) - **Components**: PascalCase, never self-closing tags - **Functions**: camelCase for functions, PascalCase for constructors/classes - **Constants**: SCREAMING_SNAKE_CASE for constants, camelCase for const values - **Files**: kebab-case for files, PascalCase for Vue components - **State**: `useState('prefix:name', () => default)` for reactive state ## Imports Order Group imports in this order (alphabetical within groups): 1. Type imports (`types/`) 2. Dependency imports (npm packages) 3. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`) 4. Local imports (`lib/`, `server/`) ```typescript // Example import order import type { Result, Ok, Err } from '~~/types/result'; import type { Foo } from 'vue'; import { ref, computed } from 'vue'; import { useFeature } from '~/composables/useFeature'; import { decrypt } from '~/utils/crypto'; import { authClient } from '~~/lib/auth-client'; ``` ## Error Handling Avoid using `throw` statements in your code. Use the `Result` type with `Ok()` and `Err()` factory functions: ```typescript import { type Result, Ok, Err } from '~~/types/result'; const myFunction(): Result { if (error) { return Err(ErrorType.SpecificError); } return Ok(data); } // Usage const result = myFunction(); if (result.ok === false) { console.error(result.error); return; } processData(result.data); ``` ## Composables Pattern All composables return reactive state + actions: ```typescript export const useFeature = () => { const state = useState('feature:state', () => defaultValue); const computedValue = computed(() => /* logic */); const actions = { async doSomething() { // implementation }, }; return { state, computedValue, ...actions }; }; ``` ## API Route Pattern ```typescript // File: server/api/endpoint.method.ts import { protectRoute } from '~/server/utils/protect'; export default defineEventHandler(async (event) => { await protectRoute(event); // implementation return { /* response */ }; }); ``` ## Database Query Pattern (Drizzle) ```typescript // Server-side queries use Drizzle ORM import { db } from '~~/server/db'; import { agents } from '~~/drizzle/schema'; // Query with relations const result = await db.query.agents.findMany({ where: eq(agents.userId, userId), with: { topics: true, messages: true }, }); // Insert with conflict handling await db.insert(agents).values(data).onConflictDoNothing(); ``` ## Authentication Flow 1. **Login**: Client uses `authClient.signIn.email()` → derives encryption key from password 2. **Session**: Better-auth creates session token → stored in cookie 3. **API Keys**: Encrypted client-side with AES-GCM (key derived from password + userId) **Key files**: `lib/auth.ts`, `lib/auth-client.ts`, `app/plugins/auth.*.ts`, `app/middleware/auth.global.ts` ## Styling System **UnoCSS** with semantic color variables in `app/assets/css/base.css`: - `--color-accent`, `--color-neutral`, `--color-text`, `--color-muted` - `--color-highlight` (for borders/hover states) - Theme-aware: `:root.dark` / `:root.light` with `color-mix()` ## Environment Variables | Variable | Purpose | |----------|---------| | `DATABASE_URL` | PostgreSQL connection string | | `BETTER_AUTH_SECRET` | Auth encryption | ## Key Composables | Composable | Purpose | |------------|---------| | `useAuth()` | Session/user state, signIn/signOut | | `useChat(id)` | Send/regenerate messages, create topics | | `useModels()` | Providers + models with auto-subscription | | `useAgents()` | User's agents with topics | | `useSidebar()` | Sidebar state + resize | | `useTheme()` | Accent/neutral theme cookies | | `useSettings()` | Settings dialog state | ## Key Routes | Route | File | |-------|------| | `/` | `app/pages/index.vue` | | `/auth/login` | `app/pages/auth/login.vue` | | `/auth/register` | `app/pages/auth/register.vue` | | `/agent/:id` | `app/pages/agent/[id]/index.vue` | | `/agent/:id/topic/:topicId` | `app/pages/agent/[id]/topic/[topicId].vue` | | `/agent/:id/profile` | `app/pages/agent/[id]/profile.vue` | ## Server API Routes | Endpoint | File | |----------|------| | `POST /api/chat/generate` | `server/api/chat/generate.post.ts` | | `POST /api/chat/cancel/:id` | `server/api/chat/cancel/[generationId].post.ts` | | `POST /api/provider/:id/models` | `server/api/provider/[providerId]/models.post.ts` | | `* /api/auth/*` | `server/api/auth/[...all].ts` | ## Known Issues (from BUGS.md) 1. Sidebar hover animation occasionally glitches on agent routes 2. Theme switcher + sidebar interaction bug ## Critical Architecture Notes ### Chat Endpoint God File - `server/api/topic/[topicId]/chat/index.post.ts` is 1263 lines handling route validation, tool definitions, streaming orchestration, DB transactions, and event emission - Files that change together: this file + `server/utils/events.ts` + `server/utils/generations.ts` - Tool definitions (bash, python, file access) should be extracted to separate module before extending ### Security: Code Execution Tools Have No Sandboxing - `bashTool` uses `exec()` and `pythonTool` uses `python3 -c` with user-controlled input — full RCE risk - Do NOT extend these tools without adding: timeouts, path restrictions, resource limits - Location: `server/api/topic/[topicId]/chat/index.post.ts` ~lines 261-455 ### Crash Risk: `todo()` Function - `todo()` literally throws `new Error('TODO')` on unhandled token types in chat handler - Handle gracefully before production deployment - Location: `server/api/topic/[topicId]/chat/index.post.ts` ~line 256 ### Module-Scoped Mutable State Limitations - `useDialog.ts` has module-scoped `actionCallback` — not SSR-safe, overwritten on concurrent dialogs - `pendingGenerations` in `server/utils/generations.ts` and in-memory event channels in `server/utils/events.ts` won't work in multi-process deployments - Fine for single-instance dev, but architectural ceiling for scaling ### Pagination Params Validated But Unused - `agents.get.ts` validates `page`/`limit` query params but never applies them to the query - All agent queries return everything regardless of pagination params ## Development Notes - **Never run dev server as agent** - just complete tasks and signal done - Always run `bunx drizzle-kit push` after schema changes - Use `protectRoute()` in all API routes for auth - All UI state that persists → use cookies (`useCookie()`) - Real-time data → SSE events via `server/utils/events.ts` - API keys → encrypted client-side before DB storage