diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..8fa527e2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# AGENTS.md + +## Cursor Cloud specific instructions + +### Services Overview + +| Service | How to run | Port | +|---------|-----------|------| +| Backend (NestJS) | `cd backend && npm run start:dev` | 4000 | +| Bot SDK | `cd bot-sdk && npm run build` | N/A (library) | + +### Backend Dev Server + +- **Start**: `cd backend && npm run start:dev` (uses nodemon + ts-node) +- **Health check**: `GET http://localhost:4000/api/v1/health` +- **API prefix**: `/api/v1` +- **WebSocket**: `ws://localhost:4000` (Socket.IO) + +### Prerequisites (must be running before backend starts) + +- **PostgreSQL** on `localhost:5432` — database `flowspace`, user `flowspace`/`flowspace` +- **Redis** on `localhost:6379` — used for Socket.IO horizontal scaling; backend gracefully falls back to in-memory adapter if unavailable + +Start services: + +```bash +sudo pg_ctlcluster 16 main start +sudo redis-server --daemonize yes +``` + +### Key Gotchas + +1. **Prisma schema requires manual relation fixes**: The `Bot` and `BotMessage` models reference `Workspace`, `Channel`, and `ChatMessage` but the reverse relation fields (`bots Bot[]`, `botMessages BotMessage[]`) must be present on those models. Run `npx prisma generate` after any schema changes. + +2. **`projectTemplates.json` required at startup**: The backend TemplateService throws at boot if `backend/config/projectTemplates.json` doesn't exist. A minimal seed file is committed. + +3. **Module DI for `JwtAuthGuard`**: Any NestJS module using `@UseGuards(JwtAuthGuard)` must import `AuthModule` (which exports `AuthService`). + +4. **`.env` location**: The backend reads environment from `backend/.env` (via `@nestjs/config`). The committed `env.development` uses Docker service hostnames; for local dev, create `.env` with `localhost` addresses (DATABASE_URL, REDIS_URL, etc.). + +5. **TypeScript checking**: `npm run build` runs `prisma generate && nest build`. For type-only checking use `npx tsc --noEmit` from the backend directory. + +6. **No ESLint configured**: The project does not have an ESLint config. TypeScript compilation (`tsc --noEmit`) is the primary static analysis. + +7. **MinIO/S3 and LiveKit are optional**: File vault and video meeting features require these services but the backend starts fine without them. + +### Test Account (local dev) + +```text +Email: dev@flowspace.app +Password: TestPass123! +``` + +Or register via `POST /api/v1/auth/register` with `{ email, password, name }`. + +### Flutter Client + +The Flutter client (`client_flutter/`) targets desktop (Windows primary) and mobile. It requires the Flutter SDK and cannot be run in headless Cloud Agent environments. Focus backend development on the NestJS API server. diff --git a/backend/config/projectTemplates.json b/backend/config/projectTemplates.json new file mode 100644 index 00000000..7935d460 --- /dev/null +++ b/backend/config/projectTemplates.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0", + "templates": [ + { + "id": "kanban", + "name": "Kanban Board", + "description": "Simple task management with columns: To Do, In Progress, Done", + "backgroundModule": "kanban", + "tools": ["tasks", "labels", "assignments"], + "defaultBoards": ["kanban"], + "defaultSettings": { + "columns": ["To Do", "In Progress", "Done"] + } + }, + { + "id": "storyboard", + "name": "Storyboard", + "description": "Visual storyboard for creative projects", + "backgroundModule": "storyboard", + "tools": ["scenes", "characters", "notes"], + "defaultBoards": ["storyboard"], + "defaultSettings": { + "layout": "grid" + } + }, + { + "id": "sprint", + "name": "Sprint Planning", + "description": "Agile sprint planning with backlog and sprint boards", + "backgroundModule": "agile", + "tools": ["tasks", "sprints", "velocity"], + "defaultBoards": ["backlog", "kanban"], + "defaultSettings": { + "sprintLength": 14, + "columns": ["Backlog", "To Do", "In Progress", "Review", "Done"] + } + } + ] +} diff --git a/backend/src/bots/bots.module.ts b/backend/src/bots/bots.module.ts index 7d8b81f3..5d8e4b89 100644 --- a/backend/src/bots/bots.module.ts +++ b/backend/src/bots/bots.module.ts @@ -5,6 +5,7 @@ import { BotsService } from './bots.service'; import { BotsController } from './bots.controller'; import { BotsGateway } from './bots.gateway'; import { BotAuthGuard } from './bots-auth.guard'; +import { AuthModule } from '../auth/auth.module'; import { SharedModule } from '../shared/shared.module'; import { PrismaModule } from '../database/prisma.module'; @@ -13,6 +14,7 @@ import { PrismaModule } from '../database/prisma.module'; imports: [ PrismaModule, SharedModule, + AuthModule, JwtModule.registerAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ diff --git a/backend/src/bots/bots.service.ts b/backend/src/bots/bots.service.ts index 9f643ee8..f05e49f5 100644 --- a/backend/src/bots/bots.service.ts +++ b/backend/src/bots/bots.service.ts @@ -7,7 +7,7 @@ import { import { randomBytes, randomUUID } from 'crypto'; import { hash, compare } from 'bcrypt'; import { PrismaService } from '../database/prisma.service'; -import { WorkspaceRole } from '@prisma/client'; +import { WorkspaceRole, BotHandlerType } from '@prisma/client'; @Injectable() export class BotsService { @@ -204,7 +204,7 @@ export class BotsService { workspaceId: string, botId: string, userId: string, - data: { command: string; description: string; handlerType?: string; handlerUrl?: string }, + data: { command: string; description: string; handlerType?: BotHandlerType; handlerUrl?: string }, ) { await this.requireAdmin(workspaceId, userId); await this.requireBotOwnership(workspaceId, botId); @@ -221,7 +221,7 @@ export class BotsService { botId, command: data.command, description: data.description, - handlerType: (data.handlerType as any) || 'WEBSOCKET', + handlerType: data.handlerType || BotHandlerType.WEBSOCKET, handlerUrl: data.handlerUrl, }, }); @@ -242,7 +242,7 @@ export class BotsService { botId: string, commandId: string, userId: string, - data: { description?: string; enabled?: boolean; handlerType?: string; handlerUrl?: string }, + data: { description?: string; enabled?: boolean; handlerType?: BotHandlerType; handlerUrl?: string }, ) { await this.requireAdmin(workspaceId, userId); await this.requireBotOwnership(workspaceId, botId); diff --git a/backend/src/bots/dto/bot.dto.ts b/backend/src/bots/dto/bot.dto.ts index da0f4aed..02b341b0 100644 --- a/backend/src/bots/dto/bot.dto.ts +++ b/backend/src/bots/dto/bot.dto.ts @@ -1,4 +1,5 @@ import { IsString, IsOptional, IsBoolean, IsEnum, MaxLength, MinLength } from 'class-validator'; +import { BotHandlerType } from '@prisma/client'; export class CreateBotDto { @IsString() @@ -53,8 +54,8 @@ export class CreateCommandDto { description!: string; @IsOptional() - @IsString() - handlerType?: string; + @IsEnum(BotHandlerType) + handlerType?: BotHandlerType; @IsOptional() @IsString() @@ -72,8 +73,8 @@ export class UpdateCommandDto { enabled?: boolean; @IsOptional() - @IsString() - handlerType?: string; + @IsEnum(BotHandlerType) + handlerType?: BotHandlerType; @IsOptional() @IsString() diff --git a/backend/src/search/search.module.ts b/backend/src/search/search.module.ts index cb7ae91f..903180e6 100644 --- a/backend/src/search/search.module.ts +++ b/backend/src/search/search.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; +import { AuthModule } from '../auth/auth.module'; import { PrismaModule } from '../database/prisma.module'; import { SharedModule } from '../shared/shared.module'; import { SearchController } from './search.controller'; import { SearchService } from './search.service'; @Module({ - imports: [PrismaModule, SharedModule], + imports: [PrismaModule, SharedModule, AuthModule], controllers: [SearchController], providers: [SearchService], }) diff --git a/bot-sdk/package-lock.json b/bot-sdk/package-lock.json new file mode 100644 index 00000000..b58a52b5 --- /dev/null +++ b/bot-sdk/package-lock.json @@ -0,0 +1,141 @@ +{ + "name": "@flowspace/bot-sdk", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@flowspace/bot-sdk", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "socket.io-client": "^4.7.0" + }, + "devDependencies": { + "typescript": "^5.3.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + } + } +}