Files
veridian/AGENTS.md
T

213 lines
6.9 KiB
Markdown

# Veridian AGENTS.md
**Nuxt 4 AI chat platform with Triplit database + 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
triplit/ # Database
├── schema.ts # Full schema with auth + app collections
└── auth-schema.ts # Auth collections (users, sessions, accounts)
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 triplit schema push` - Push schema changes to database
## 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. Triplit imports (`#triplit/`, `@triplit/`)
4. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`)
5. Local imports (`lib/`, `server/`)
```typescript
// Example import order
import type { Result, Ok, Err } from '~~/types/result';
import type { Entity } from '@triplit/client';
import type schema from '#triplit/schema';
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 (Triplit)
```typescript
// Client queries use useQuery() with auto-includes
useQuery('collection', triplit, triplit.query('collection').Include('relation'));
// Server-side
import { httpClient } from '~~/server/lib/triplit';
await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', providerId));
```
## 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. **Triplit**: Session token passed to `triplit.startSession(token)` for DB access
4. **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 |
|----------|---------|
| `TRIPLIT_SERVICE_TOKEN` | Admin DB access |
| `NUXT_TRIPLIT_ANON_TOKEN` | Anonymous DB access |
| `BETTER_AUTH_SECRET` | Auth encryption |
| `NUXT_PUBLIC_TRIPLIT_URL` | DB server URL |
## 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. Triplit occasionally makes duplicate connections (race condition)
2. Sidebar hover animation occasionally glitches on agent routes
3. Theme switcher + sidebar interaction bug
## Development Notes
- **Never run dev server as agent** - just complete tasks and signal done
- Always run `bunx triplit schema push` after schema changes
- Use `protectRoute()` in all API routes for auth
- All UI state that persists → use cookies (`useCookie()`)
- Real-time data → Triplit subscriptions via `useQuery()`
- API keys → encrypted client-side before DB storage