Initial monorepo structure: NestJS API + Next.js Web + Rust Agent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude 2026-02-18 16:36:22 +05:00
parent 71b4dc8e30
commit 703c254074
23 changed files with 392 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
dist/
.next/
target/
.env
*.log

23
CLAUDE.md Normal file
View File

@ -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

14
apps/agent/Cargo.toml Normal file
View File

@ -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"

11
apps/agent/src/main.rs Normal file
View File

@ -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");
}

5
apps/api/nest-cli.json Normal file
View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

36
apps/api/package.json Normal file
View File

@ -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"
}
}

View File

@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule {}

13
apps/api/src/main.ts Normal file
View File

@ -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();

21
apps/api/tsconfig.json Normal file
View File

@ -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
}
}

7
apps/web/next.config.ts Normal file
View File

@ -0,0 +1,7 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;

21
apps/web/package.json Normal file
View File

@ -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"
}
}

View File

@ -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 (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@ -0,0 +1,8 @@
export default function Home() {
return (
<main style={{ padding: '2rem', fontFamily: 'system-ui' }}>
<h1>Reckue Dev</h1>
<p>Platform is starting up...</p>
</main>
);
}

21
apps/web/tsconfig.json Normal file
View File

@ -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"]
}

2
deploy/.env.example Normal file
View File

@ -0,0 +1,2 @@
DATABASE_PASSWORD=
JWT_SECRET=

17
deploy/Dockerfile.api Normal file
View File

@ -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"]

15
deploy/Dockerfile.web Normal file
View File

@ -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"]

32
deploy/docker-compose.yml Normal file
View File

@ -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

View File

@ -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;
}
}

11
package.json Normal file
View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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 };

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2021",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}