From 703c254074534e9a9223b2ee37f4701a8edfda10 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 18 Feb 2026 16:36:22 +0500 Subject: [PATCH] Initial monorepo structure: NestJS API + Next.js Web + Rust Agent Co-Authored-By: Claude Opus 4.6 --- .gitignore | 6 ++++ CLAUDE.md | 23 ++++++++++++++++ apps/agent/Cargo.toml | 14 ++++++++++ apps/agent/src/main.rs | 11 ++++++++ apps/api/nest-cli.json | 5 ++++ apps/api/package.json | 36 ++++++++++++++++++++++++ apps/api/src/app.module.ts | 8 ++++++ apps/api/src/main.ts | 13 +++++++++ apps/api/tsconfig.json | 21 ++++++++++++++ apps/web/next.config.ts | 7 +++++ apps/web/package.json | 21 ++++++++++++++ apps/web/src/app/layout.tsx | 12 ++++++++ apps/web/src/app/page.tsx | 8 ++++++ apps/web/tsconfig.json | 21 ++++++++++++++ deploy/.env.example | 2 ++ deploy/Dockerfile.api | 17 ++++++++++++ deploy/Dockerfile.web | 15 ++++++++++ deploy/docker-compose.yml | 32 +++++++++++++++++++++ deploy/nginx/reckue-dev.conf | 33 ++++++++++++++++++++++ package.json | 11 ++++++++ packages/shared/package.json | 13 +++++++++ packages/shared/src/index.ts | 52 +++++++++++++++++++++++++++++++++++ packages/shared/tsconfig.json | 11 ++++++++ 23 files changed, 392 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 apps/agent/Cargo.toml create mode 100644 apps/agent/src/main.rs create mode 100644 apps/api/nest-cli.json create mode 100644 apps/api/package.json create mode 100644 apps/api/src/app.module.ts create mode 100644 apps/api/src/main.ts create mode 100644 apps/api/tsconfig.json create mode 100644 apps/web/next.config.ts create mode 100644 apps/web/package.json create mode 100644 apps/web/src/app/layout.tsx create mode 100644 apps/web/src/app/page.tsx create mode 100644 apps/web/tsconfig.json create mode 100644 deploy/.env.example create mode 100644 deploy/Dockerfile.api create mode 100644 deploy/Dockerfile.web create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/nginx/reckue-dev.conf create mode 100644 package.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..098a399 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.next/ +target/ +.env +*.log diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b0c8b02 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,23 @@ +# Reckue Dev + +## Project Structure +- `apps/api` -- NestJS backend (REST + WebSocket) +- `apps/web` -- Next.js frontend +- `apps/agent` -- Rust agent for Windows machines +- `packages/shared` -- Shared TypeScript types +- `tools/` -- MCP servers and utilities (git submodules) +- `deploy/` -- Docker Compose, Nginx configs, Dockerfiles + +## Development +- API: `npm run dev:api` (port 3001) +- Web: `npm run dev:web` (port 3000) +- Agent: `cd apps/agent && cargo run` + +## Servers +- Git: git.reckue.com (176.53.162.120) +- Deploy: app.reckue.com (72.56.119.246) +- Database: 72.56.119.162 (PostgreSQL) + +## Redmine +- Project: reckue-dev +- URL: https://redmine.reckue.com/projects/reckue-dev diff --git a/apps/agent/Cargo.toml b/apps/agent/Cargo.toml new file mode 100644 index 0000000..284cb9a --- /dev/null +++ b/apps/agent/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "reckue-agent" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +tokio-tungstenite = { version = "0.24", features = ["native-tls"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["v4"] } +tracing = "0.1" +tracing-subscriber = "0.3" +portable-pty = "0.8" diff --git a/apps/agent/src/main.rs b/apps/agent/src/main.rs new file mode 100644 index 0000000..4509082 --- /dev/null +++ b/apps/agent/src/main.rs @@ -0,0 +1,11 @@ +use tracing::info; + +#[tokio::main] +async fn main() { + tracing_subscriber::init(); + info!("Reckue Agent starting..."); + // TODO: WebSocket connection to Control Plane + // TODO: Machine registration + heartbeats + // TODO: PTY session manager + info!("Agent ready"); +} diff --git a/apps/api/nest-cli.json b/apps/api/nest-cli.json new file mode 100644 index 0000000..2566481 --- /dev/null +++ b/apps/api/nest-cli.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src" +} diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..c4af49e --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,36 @@ +{ + "name": "@reckue-dev/api", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/main", + "start:prod": "node dist/main" + }, + "dependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.0", + "@nestjs/typeorm": "^0.3.0", + "typeorm": "^0.3.0", + "pg": "^8.13.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.0", + "bcrypt": "^5.1.0", + "class-validator": "^0.14.0", + "class-transformer": "^0.5.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.0" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts new file mode 100644 index 0000000..ee5f2c9 --- /dev/null +++ b/apps/api/src/app.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; + +@Module({ + imports: [], + controllers: [], + providers: [], +}) +export class AppModule {} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 0000000..a302bcc --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,13 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + app.enableCors(); + app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); + app.setGlobalPrefix('api'); + await app.listen(process.env.PORT ?? 3001); + console.log(`API running on port ${process.env.PORT ?? 3001}`); +} +bootstrap(); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..a1c778d --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts new file mode 100644 index 0000000..94647ad --- /dev/null +++ b/apps/web/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + output: 'standalone', +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..579dcf5 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,21 @@ +{ + "name": "@reckue-dev/web", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev --port 3000", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "^15.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "typescript": "^5.7.0" + } +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..10b333e --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Reckue Dev', + description: 'Project management & Claude Code sessions', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..302a652 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,8 @@ +export default function Home() { + return ( +
+

Reckue Dev

+

Platform is starting up...

+
+ ); +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..54446d5 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { "@/*": ["./src/*"] } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..d5500ca --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,2 @@ +DATABASE_PASSWORD= +JWT_SECRET= diff --git a/deploy/Dockerfile.api b/deploy/Dockerfile.api new file mode 100644 index 0000000..39bf25b --- /dev/null +++ b/deploy/Dockerfile.api @@ -0,0 +1,17 @@ +FROM node:22-alpine AS builder +WORKDIR /app +COPY package.json ./ +COPY apps/api/package.json apps/api/ +COPY packages/shared/package.json packages/shared/ +RUN npm install --workspace=apps/api --workspace=packages/shared +COPY apps/api apps/api +COPY packages/shared packages/shared +RUN npm run build --workspace=packages/shared +RUN npm run build --workspace=apps/api + +FROM node:22-alpine +WORKDIR /app +COPY --from=builder /app/apps/api/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +EXPOSE 3001 +CMD ["node", "dist/main"] diff --git a/deploy/Dockerfile.web b/deploy/Dockerfile.web new file mode 100644 index 0000000..e262b30 --- /dev/null +++ b/deploy/Dockerfile.web @@ -0,0 +1,15 @@ +FROM node:22-alpine AS builder +WORKDIR /app +COPY package.json ./ +COPY apps/web/package.json apps/web/ +RUN npm install --workspace=apps/web +COPY apps/web apps/web +RUN npm run build --workspace=apps/web + +FROM node:22-alpine +WORKDIR /app +COPY --from=builder /app/apps/web/.next/standalone ./ +COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=builder /app/apps/web/public ./apps/web/public +EXPOSE 3000 +CMD ["node", "apps/web/server.js"] diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..751d72b --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + api: + build: + context: .. + dockerfile: deploy/Dockerfile.api + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - DATABASE_HOST=72.56.119.162 + - DATABASE_PORT=5432 + - DATABASE_NAME=reckue_dev + - DATABASE_USER=reckue + - DATABASE_PASSWORD=${DATABASE_PASSWORD} + - JWT_SECRET=${JWT_SECRET} + restart: unless-stopped + + web: + build: + context: .. + dockerfile: deploy/Dockerfile.web + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - NEXT_PUBLIC_API_URL=https://app.reckue.com/api + restart: unless-stopped + depends_on: + - api diff --git a/deploy/nginx/reckue-dev.conf b/deploy/nginx/reckue-dev.conf new file mode 100644 index 0000000..bb66875 --- /dev/null +++ b/deploy/nginx/reckue-dev.conf @@ -0,0 +1,33 @@ +server { + listen 80; + server_name app.reckue.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name app.reckue.com; + + ssl_certificate /etc/letsencrypt/live/app.reckue.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/app.reckue.com/privkey.pem; + + location /api { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ba3d808 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "reckue-dev", + "private": true, + "workspaces": ["apps/*", "packages/*"], + "scripts": { + "dev:api": "npm run dev --workspace=apps/api", + "dev:web": "npm run dev --workspace=apps/web", + "build:api": "npm run build --workspace=apps/api", + "build:web": "npm run build --workspace=apps/web" + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..b94c296 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,13 @@ +{ + "name": "@reckue-dev/shared", + "version": "0.0.1", + "private": true, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc" + }, + "devDependencies": { + "typescript": "^5.7.0" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..bd8ec84 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,52 @@ +export interface User { + id: string; + email: string; + name: string; + role: 'owner' | 'admin' | 'manager' | 'developer'; + createdAt: Date; +} + +export interface Machine { + id: string; + name: string; + hostname: string; + status: 'online' | 'offline'; + lastHeartbeat: Date; +} + +export interface Project { + id: string; + name: string; + description: string; + ownerId: string; + createdAt: Date; +} + +export interface Workspace { + id: string; + projectId: string; + machineId: string; + path: string; + gitUrl: string; + branch: string; +} + +export interface Session { + id: string; + workspaceId: string; + status: 'running' | 'idle' | 'stopped' | 'error'; + startedAt: Date; + stoppedAt?: Date; +} + +export type AgentMessage = + | { type: 'register'; machineId: string; hostname: string; os: string } + | { type: 'heartbeat'; machineId: string; timestamp: string } + | { type: 'session_output'; sessionId: string; data: string } + | { type: 'session_status'; sessionId: string; status: Session['status'] }; + +export type ServerMessage = + | { type: 'session_start'; sessionId: string; workspaceId: string; command: string } + | { type: 'session_stop'; sessionId: string } + | { type: 'session_input'; sessionId: string; data: string } + | { type: 'workspace_init'; workspaceId: string; gitUrl: string; branch: string }; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..039d254 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2021", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +}