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:
parent
71b4dc8e30
commit
703c254074
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.next/
|
||||||
|
target/
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
23
CLAUDE.md
Normal file
23
CLAUDE.md
Normal 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
14
apps/agent/Cargo.toml
Normal 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
11
apps/agent/src/main.rs
Normal 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
5
apps/api/nest-cli.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src"
|
||||||
|
}
|
||||||
36
apps/api/package.json
Normal file
36
apps/api/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
apps/api/src/app.module.ts
Normal file
8
apps/api/src/app.module.ts
Normal 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
13
apps/api/src/main.ts
Normal 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
21
apps/api/tsconfig.json
Normal 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
7
apps/web/next.config.ts
Normal 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
21
apps/web/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
apps/web/src/app/layout.tsx
Normal file
12
apps/web/src/app/layout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
apps/web/src/app/page.tsx
Normal file
8
apps/web/src/app/page.tsx
Normal 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
21
apps/web/tsconfig.json
Normal 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
2
deploy/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
DATABASE_PASSWORD=
|
||||||
|
JWT_SECRET=
|
||||||
17
deploy/Dockerfile.api
Normal file
17
deploy/Dockerfile.api
Normal 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
15
deploy/Dockerfile.web
Normal 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
32
deploy/docker-compose.yml
Normal 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
|
||||||
33
deploy/nginx/reckue-dev.conf
Normal file
33
deploy/nginx/reckue-dev.conf
Normal 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
11
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/shared/package.json
Normal file
13
packages/shared/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
packages/shared/src/index.ts
Normal file
52
packages/shared/src/index.ts
Normal 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 };
|
||||||
11
packages/shared/tsconfig.json
Normal file
11
packages/shared/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2021",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user