Files

190 lines
5.9 KiB
Markdown

# 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)
├── 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<T, E>` type with `Ok()` and `Err()` factory functions:
```typescript
import { type Result, Ok, Err } from '~~/types/result';
const myFunction(): Result<ReturnType, ErrorType> {
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`, `app/plugins/auth.*.ts`, `app/middleware/auth.global.ts`, `app/composables/useAuth.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 |
## 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