190 lines
5.9 KiB
Markdown
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
|