Initialize Aidoit v0.1 monorepo
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,18 @@
|
||||
AIDOIT_ENV=development
|
||||
AIDOIT_ADMIN_TOKEN=replace-with-a-long-random-token
|
||||
|
||||
POSTGRES_DB=aidoit
|
||||
POSTGRES_USER=aidoit
|
||||
POSTGRES_PASSWORD=replace-me
|
||||
DATABASE_URL=postgresql+psycopg://aidoit:replace-me@localhost:5432/aidoit
|
||||
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
MINIO_ROOT_USER=aidoit
|
||||
MINIO_ROOT_PASSWORD=replace-with-a-long-password
|
||||
MINIO_ENDPOINT=http://localhost:9000
|
||||
MINIO_BUCKET=aidoit-artifacts
|
||||
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
WEB_ORIGIN=http://localhost:3000
|
||||
MSP_ORIGIN=http://localhost:3001
|
||||
@@ -0,0 +1,32 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install --no-frozen-lockfile
|
||||
- run: pnpm typecheck
|
||||
- run: pnpm build
|
||||
|
||||
api:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- run: pip install -r apps/api/requirements-dev.txt
|
||||
- run: pytest apps/api/tests
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
.turbo/
|
||||
.next/
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
*.log
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
.venv/
|
||||
venv/
|
||||
target/
|
||||
*.db
|
||||
*.sqlite3
|
||||
@@ -0,0 +1,29 @@
|
||||
.PHONY: install dev up down logs build lint typecheck test
|
||||
|
||||
install:
|
||||
pnpm install
|
||||
cd apps/api && python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
|
||||
|
||||
dev:
|
||||
pnpm dev
|
||||
|
||||
up:
|
||||
docker compose -f infrastructure/docker/compose.yml up -d
|
||||
|
||||
down:
|
||||
docker compose -f infrastructure/docker/compose.yml down
|
||||
|
||||
logs:
|
||||
docker compose -f infrastructure/docker/compose.yml logs -f
|
||||
|
||||
build:
|
||||
pnpm build
|
||||
|
||||
lint:
|
||||
pnpm lint
|
||||
|
||||
typecheck:
|
||||
pnpm typecheck
|
||||
|
||||
test:
|
||||
pnpm test
|
||||
@@ -0,0 +1,66 @@
|
||||
# Aidoit
|
||||
|
||||
Aidoit is a single-user personal AI operating system for building applications,
|
||||
running AI teams, and managing infrastructure.
|
||||
|
||||
## Repository structure
|
||||
|
||||
```text
|
||||
apps/
|
||||
web/ Main web development dashboard
|
||||
msp/ Infrastructure management dashboard
|
||||
api/ Shared modular REST API
|
||||
desktop/ Tauri desktop application workspace
|
||||
|
||||
packages/
|
||||
ui/ Shared React components and design tokens
|
||||
contracts/ Shared domain and API contracts
|
||||
sdk/ Typed API client
|
||||
config/ Shared TypeScript configuration
|
||||
|
||||
infrastructure/
|
||||
docker/ Local Docker Compose stack
|
||||
nginx/ Reverse-proxy examples
|
||||
|
||||
docs/
|
||||
architecture/
|
||||
decisions/
|
||||
roadmap/
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 22+
|
||||
- pnpm 10+
|
||||
- Python 3.13+
|
||||
- Docker with Docker Compose
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
pnpm install
|
||||
docker compose -f infrastructure/docker/compose.yml up -d postgres redis minio
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Services:
|
||||
|
||||
- Web dashboard: http://localhost:3000
|
||||
- MSP dashboard: http://localhost:3001
|
||||
- API: http://localhost:8000
|
||||
- OpenAPI: http://localhost:8000/docs
|
||||
- MinIO console: http://localhost:9001
|
||||
|
||||
## First production targets
|
||||
|
||||
- `web.aidoit.top`
|
||||
- `msp.aidoit.top`
|
||||
- `api.aidoit.top`
|
||||
- desktop application communicating with the shared API
|
||||
|
||||
## Status
|
||||
|
||||
This repository is the professional v0.1 foundation. It intentionally starts as a
|
||||
modular monolith and leaves room for later extraction of workers or services when
|
||||
real operational needs justify it.
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM python:3.13-slim AS runtime
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app ./app
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,20 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
aidoit_env: str = "development"
|
||||
aidoit_admin_token: str = "replace-me"
|
||||
database_url: str = "sqlite:///./aidoit.db"
|
||||
redis_url: str = "redis://localhost:6379/0"
|
||||
web_origin: str = "http://localhost:3000"
|
||||
msp_origin: str = "http://localhost:3001"
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -0,0 +1,32 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.core.config import settings
|
||||
from app.modules.health.router import router as health_router
|
||||
from app.modules.projects.router import router as projects_router
|
||||
|
||||
app = FastAPI(
|
||||
title="Aidoit API",
|
||||
version="0.1.0",
|
||||
description="Shared REST API for the Aidoit personal AI operating system.",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[settings.web_origin, settings.msp_origin],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(health_router)
|
||||
app.include_router(projects_router, prefix="/api/v1")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root() -> dict[str, str]:
|
||||
return {
|
||||
"name": "Aidoit API",
|
||||
"version": "0.1.0",
|
||||
"docs": "/docs",
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "aidoit-api",
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
|
||||
from app.modules.projects.schemas import ProjectCreate, ProjectRead
|
||||
from app.modules.projects.service import project_service
|
||||
|
||||
router = APIRouter(prefix="/projects", tags=["projects"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[ProjectRead])
|
||||
async def list_projects() -> list[ProjectRead]:
|
||||
return project_service.list()
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ProjectRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_project(payload: ProjectCreate) -> ProjectRead:
|
||||
return project_service.create(payload)
|
||||
|
||||
|
||||
@router.get("/{project_id}", response_model=ProjectRead)
|
||||
async def get_project(project_id: int) -> ProjectRead:
|
||||
project = project_service.get(project_id)
|
||||
if project is None:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
return project
|
||||
@@ -0,0 +1,19 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
ProductType = Literal["web", "desktop", "msp"]
|
||||
|
||||
|
||||
class ProjectCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=120)
|
||||
description: str = Field(default="", max_length=2000)
|
||||
product: ProductType = "web"
|
||||
|
||||
|
||||
class ProjectRead(ProjectCreate):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,31 @@
|
||||
from datetime import UTC, datetime
|
||||
from itertools import count
|
||||
|
||||
from app.modules.projects.schemas import ProjectCreate, ProjectRead
|
||||
|
||||
|
||||
class ProjectService:
|
||||
def __init__(self) -> None:
|
||||
self._ids = count(1)
|
||||
self._projects: list[ProjectRead] = []
|
||||
|
||||
def list(self) -> list[ProjectRead]:
|
||||
return self._projects.copy()
|
||||
|
||||
def create(self, payload: ProjectCreate) -> ProjectRead:
|
||||
project = ProjectRead(
|
||||
id=next(self._ids),
|
||||
created_at=datetime.now(UTC),
|
||||
**payload.model_dump(),
|
||||
)
|
||||
self._projects.append(project)
|
||||
return project
|
||||
|
||||
def get(self, project_id: int) -> ProjectRead | None:
|
||||
return next(
|
||||
(project for project in self._projects if project.id == project_id),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
project_service = ProjectService()
|
||||
@@ -0,0 +1,31 @@
|
||||
[project]
|
||||
name = "aidoit-api"
|
||||
version = "0.1.0"
|
||||
description = "Shared REST API for Aidoit"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastapi>=0.115",
|
||||
"uvicorn[standard]>=0.34",
|
||||
"pydantic-settings>=2.7",
|
||||
"sqlalchemy>=2.0",
|
||||
"psycopg[binary]>=3.2",
|
||||
"alembic>=1.14",
|
||||
"redis>=5.2",
|
||||
"httpx>=0.28"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.3",
|
||||
"pytest-asyncio>=0.25",
|
||||
"ruff>=0.9",
|
||||
"mypy>=1.14"
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py313"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["."]
|
||||
testpaths = ["tests"]
|
||||
@@ -0,0 +1 @@
|
||||
-e .[dev]
|
||||
@@ -0,0 +1 @@
|
||||
-e .
|
||||
@@ -0,0 +1,11 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_health() -> None:
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "ok"
|
||||
@@ -0,0 +1,16 @@
|
||||
# Aidoit Desktop
|
||||
|
||||
This directory is reserved for the native Tauri application.
|
||||
|
||||
Planned capabilities:
|
||||
|
||||
- API authentication and synchronization
|
||||
- local filesystem access
|
||||
- shell execution
|
||||
- Git operations
|
||||
- Docker operations
|
||||
- local Ollama integration
|
||||
- native builds for macOS, Windows and Linux
|
||||
|
||||
Tauri initialization is intentionally deferred until the shared contracts and
|
||||
desktop security boundaries are finalized.
|
||||
@@ -0,0 +1,16 @@
|
||||
import "@aidoit/ui/styles.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "Aidoit MSP",
|
||||
description: "Infrastructure management workspace",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default function MspHomePage() {
|
||||
return (
|
||||
<main style={{ padding: "48px", maxWidth: "1000px", margin: "0 auto" }}>
|
||||
<p style={{ color: "var(--accent)", fontWeight: 800 }}>AIDOIT MSP</p>
|
||||
<h1>Infrastructure workspace</h1>
|
||||
<p style={{ color: "var(--muted)" }}>
|
||||
VPS, Proxmox, Docker, LXC, monitoring, backups and deployments will live here.
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const config: NextConfig = {
|
||||
output: "standalone",
|
||||
transpilePackages: ["@aidoit/ui", "@aidoit/contracts", "@aidoit/sdk"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@aidoit/msp",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"build": "next build",
|
||||
"start": "next start --port 3001",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "echo \"No MSP tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aidoit/contracts": "workspace:*",
|
||||
"@aidoit/sdk": "workspace:*",
|
||||
"@aidoit/ui": "workspace:*",
|
||||
"next": "^15.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aidoit/config": "workspace:*",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@aidoit/config/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
.app-shell {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 26px 20px;
|
||||
border-right: 1px solid var(--border);
|
||||
background: rgba(6, 14, 22, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 34px;
|
||||
}
|
||||
|
||||
.brand strong,
|
||||
.brand span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.brand span {
|
||||
margin-top: 3px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 13px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #041019;
|
||||
background: linear-gradient(135deg, #83dcff, #4ca3ff);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--muted);
|
||||
padding: 11px 12px;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav a:hover,
|
||||
nav a.active {
|
||||
color: var(--text);
|
||||
background: rgba(87, 199, 255, 0.1);
|
||||
}
|
||||
|
||||
.api-status {
|
||||
margin-top: auto;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.api-status span,
|
||||
.feed-item > span {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 7px;
|
||||
border-radius: 50%;
|
||||
background: #5be18a;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
padding: 38px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(30px, 4vw, 48px);
|
||||
margin: 3px 0 10px;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.hero > div > p:last-child,
|
||||
.empty-state p,
|
||||
.feed-item p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero button {
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
padding: 13px 18px;
|
||||
background: var(--accent);
|
||||
color: #041019;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
color: var(--accent);
|
||||
font-weight: 800;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 0.8fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
min-height: 330px;
|
||||
padding: 22px;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(16, 30, 44, 0.96), rgba(10, 22, 32, 0.96));
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.panel h2 {
|
||||
margin: 3px 0 0;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-heading a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
min-height: 240px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state > div {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 18px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: rgba(87, 199, 255, 0.1);
|
||||
color: var(--accent);
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.feed-item {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.feed-item p {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.workspace {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Metadata } from "next";
|
||||
import "@aidoit/ui/styles.css";
|
||||
import "./app.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Aidoit",
|
||||
description: "Personal AI operating system",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { StatCard } from "@aidoit/ui";
|
||||
|
||||
const navigation = [
|
||||
"Overview",
|
||||
"Projects",
|
||||
"Runs",
|
||||
"Tasks",
|
||||
"Teams",
|
||||
"Tools",
|
||||
"Deployments",
|
||||
"Servers",
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="app-shell">
|
||||
<aside className="sidebar">
|
||||
<div className="brand">
|
||||
<div className="brand-mark">A</div>
|
||||
<div>
|
||||
<strong>Aidoit</strong>
|
||||
<span>Personal AI OS</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
{navigation.map((item, index) => (
|
||||
<a className={index === 0 ? "active" : ""} href="#" key={item}>
|
||||
{item}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="api-status">
|
||||
<span />
|
||||
Development environment
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="workspace">
|
||||
<header className="hero">
|
||||
<div>
|
||||
<p className="eyebrow">AIDOIT CONTROL CENTER</p>
|
||||
<h1>Good evening, Pavel.</h1>
|
||||
<p>
|
||||
Build applications, orchestrate AI teams, and manage infrastructure
|
||||
from one workspace.
|
||||
</p>
|
||||
</div>
|
||||
<button type="button">New project</button>
|
||||
</header>
|
||||
|
||||
<section className="stats-grid" aria-label="Workspace statistics">
|
||||
<StatCard label="Projects" value="0" detail="Web, desktop and MSP" />
|
||||
<StatCard label="Active runs" value="0" detail="Harness executions" />
|
||||
<StatCard label="Pending tasks" value="0" detail="Waiting for a team" />
|
||||
<StatCard label="Deployments" value="0" detail="Preview and production" />
|
||||
</section>
|
||||
|
||||
<section className="content-grid">
|
||||
<article className="panel">
|
||||
<div className="panel-heading">
|
||||
<div>
|
||||
<p className="eyebrow">WORKSPACE</p>
|
||||
<h2>Recent projects</h2>
|
||||
</div>
|
||||
<a href="#">View all</a>
|
||||
</div>
|
||||
<div className="empty-state">
|
||||
<div>+</div>
|
||||
<h3>No projects yet</h3>
|
||||
<p>Create the first project and assign it to an AI team.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article className="panel">
|
||||
<p className="eyebrow">ACTIVITY</p>
|
||||
<h2>Harness feed</h2>
|
||||
<div className="feed-item">
|
||||
<span />
|
||||
<div>
|
||||
<strong>Platform initialized</strong>
|
||||
<p>Professional monorepo foundation is ready.</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const config: NextConfig = {
|
||||
output: "standalone",
|
||||
transpilePackages: ["@aidoit/ui", "@aidoit/contracts", "@aidoit/sdk"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@aidoit/web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3000",
|
||||
"build": "next build",
|
||||
"start": "next start --port 3000",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "echo \"No web tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aidoit/contracts": "workspace:*",
|
||||
"@aidoit/sdk": "workspace:*",
|
||||
"@aidoit/ui": "workspace:*",
|
||||
"next": "^15.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aidoit/config": "workspace:*",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@aidoit/config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Architecture overview
|
||||
|
||||
## Principles
|
||||
|
||||
1. Single user, but authentication is still mandatory.
|
||||
2. One shared backend for all products.
|
||||
3. Modular monolith before microservices.
|
||||
4. AI harness remains independent from UI applications.
|
||||
5. Tool access is explicitly permissioned per team.
|
||||
6. Long-running work is executed asynchronously.
|
||||
7. Generated artifacts are stored outside the relational database.
|
||||
|
||||
## Products
|
||||
|
||||
### Web
|
||||
|
||||
Application development dashboard for projects, prompts, runs, tasks, preview
|
||||
environments, reviews and deployments.
|
||||
|
||||
### Desktop
|
||||
|
||||
Native Tauri application for local filesystem, shell, Git, Docker, Ollama and
|
||||
desktop application builds.
|
||||
|
||||
### MSP
|
||||
|
||||
Infrastructure dashboard for VPS, Proxmox, LXC, VM, Docker, firewall, reverse
|
||||
proxy, monitoring, updates and backups.
|
||||
|
||||
## Core backend modules
|
||||
|
||||
- authentication
|
||||
- projects
|
||||
- runs
|
||||
- tasks
|
||||
- teams
|
||||
- tools
|
||||
- artifacts
|
||||
- builds
|
||||
- deployments
|
||||
- servers
|
||||
- monitoring
|
||||
- audit log
|
||||
|
||||
## Data systems
|
||||
|
||||
- PostgreSQL: relational source of truth
|
||||
- Redis: queues, locks and ephemeral state
|
||||
- MinIO: generated files, logs and build artifacts
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR 0001: Start as a modular monolith
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Decision
|
||||
|
||||
The first production version uses a single FastAPI deployment with explicit
|
||||
module boundaries.
|
||||
|
||||
## Reasons
|
||||
|
||||
- one operator
|
||||
- easier local development
|
||||
- simpler deployment and backups
|
||||
- no premature distributed systems complexity
|
||||
- modules can be extracted later when justified by real load or isolation needs
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR 0002: Single-user security model
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Decision
|
||||
|
||||
Aidoit is single-user software, but no sensitive endpoint is anonymously
|
||||
accessible in production.
|
||||
|
||||
Initial access:
|
||||
|
||||
- Tailscale for private network access
|
||||
- bearer token for development
|
||||
- later PocketID or Authentik for browser login
|
||||
- separate execution permissions for every AI team
|
||||
- immutable audit records for tool calls and infrastructure changes
|
||||
@@ -0,0 +1,36 @@
|
||||
# v0.1 roadmap
|
||||
|
||||
## Foundation
|
||||
|
||||
- monorepo and CI
|
||||
- shared design system
|
||||
- shared API contracts and SDK
|
||||
- PostgreSQL migrations
|
||||
- authentication
|
||||
- project CRUD
|
||||
|
||||
## Harness
|
||||
|
||||
- team definitions
|
||||
- tool registry
|
||||
- task graph
|
||||
- run execution
|
||||
- event streaming
|
||||
- logs and artifacts
|
||||
|
||||
## Development workflow
|
||||
|
||||
- Gitea integration
|
||||
- repository checkout
|
||||
- isolated workspaces
|
||||
- preview Docker builds
|
||||
- code review flow
|
||||
- deployment approvals
|
||||
|
||||
## MSP
|
||||
|
||||
- server inventory
|
||||
- SSH connection profiles
|
||||
- Docker and Proxmox adapters
|
||||
- monitoring integration
|
||||
- backups and update plans
|
||||
@@ -0,0 +1,66 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ../../apps/api
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ../../.env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
MINIO_ENDPOINT: http://minio:9000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
@@ -0,0 +1,10 @@
|
||||
# Reverse proxy
|
||||
|
||||
Production proxy hosts should route:
|
||||
|
||||
- `web.aidoit.top` to the web application
|
||||
- `msp.aidoit.top` to the MSP application
|
||||
- `api.aidoit.top` to the API
|
||||
|
||||
For the first deployment, Nginx Proxy Manager can be used. Keep the API and
|
||||
databases on a private Docker network and expose only the reverse proxy.
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "aidoit",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.0.0",
|
||||
"scripts": {
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build",
|
||||
"lint": "turbo lint",
|
||||
"typecheck": "turbo typecheck",
|
||||
"test": "turbo test",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"dev:web": "pnpm --filter @aidoit/web dev",
|
||||
"dev:msp": "pnpm --filter @aidoit/msp dev",
|
||||
"dev:api": "cd apps/api && python -m uvicorn app.main:app --reload --port 8000"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.0",
|
||||
"turbo": "^2.5.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [{ "name": "next" }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@aidoit/config",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"*.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@aidoit/contracts",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "echo \"No contract tests yet\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aidoit/config": "workspace:*",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export type ProductType = "web" | "desktop" | "msp";
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
product: ProductType;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateProjectRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
product?: ProductType;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@aidoit/config/base.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@aidoit/sdk",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "echo \"No SDK tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aidoit/contracts": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aidoit/config": "workspace:*",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { CreateProjectRequest, Project } from "@aidoit/contracts";
|
||||
|
||||
export class AidoitClient {
|
||||
public constructor(
|
||||
private readonly baseUrl: string,
|
||||
private readonly token?: string,
|
||||
) {}
|
||||
|
||||
private async request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${this.baseUrl}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
|
||||
...init?.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Aidoit API request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
public listProjects(): Promise<Project[]> {
|
||||
return this.request<Project[]>("/api/v1/projects");
|
||||
}
|
||||
|
||||
public createProject(payload: CreateProjectRequest): Promise<Project> {
|
||||
return this.request<Project>("/api/v1/projects", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@aidoit/config/base.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@aidoit/ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./styles.css": "./src/styles.css"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "echo \"No UI tests yet\""
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aidoit/config": "workspace:*",
|
||||
"@types/react": "^19.0.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { StatCard } from "./stat-card";
|
||||
@@ -0,0 +1,15 @@
|
||||
type StatCardProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
detail: string;
|
||||
};
|
||||
|
||||
export function StatCard({ label, value, detail }: StatCardProps) {
|
||||
return (
|
||||
<article className="aidoit-stat-card">
|
||||
<span>{label}</span>
|
||||
<strong>{value}</strong>
|
||||
<p>{detail}</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--background: #071019;
|
||||
--surface: #0d1824;
|
||||
--surface-soft: #101e2c;
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--text: #eef6ff;
|
||||
--muted: #8ca0b5;
|
||||
--accent: #57c7ff;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
radial-gradient(circle at 80% 0%, rgba(45, 139, 196, 0.12), transparent 28%),
|
||||
var(--background);
|
||||
color: var(--text);
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.aidoit-stat-card {
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(16, 30, 44, 0.96), rgba(10, 22, 32, 0.96));
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.aidoit-stat-card span,
|
||||
.aidoit-stat-card p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.aidoit-stat-card strong {
|
||||
display: block;
|
||||
font-size: 34px;
|
||||
margin: 18px 0 5px;
|
||||
}
|
||||
|
||||
.aidoit-stat-card p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@aidoit/config/react-library.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- apps/web
|
||||
- apps/msp
|
||||
- packages/*
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"ui": "tui",
|
||||
"tasks": {
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [".next/**", "dist/**", "! .next/cache/**"]
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^lint"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^typecheck"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["coverage/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user