Initialize Aidoit v0.1 monorepo
CI / frontend (push) Canceled after 0s
CI / api (push) Canceled after 0s

This commit is contained in:
Pavel Kerndl
2026-07-27 18:27:37 +02:00
commit c0b013f067
65 changed files with 1323 additions and 0 deletions
+15
View File
@@ -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
+18
View File
@@ -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
+32
View File
@@ -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
View File
@@ -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
+29
View File
@@ -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
+66
View File
@@ -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.
+15
View File
@@ -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"]
View File
View File
+20
View File
@@ -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()
+32
View File
@@ -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",
}
View File
View File
+11
View File
@@ -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",
}
+28
View File
@@ -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
+19
View File
@@ -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
+31
View File
@@ -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()
View File
View File
View File
View File
+31
View File
@@ -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"]
+1
View File
@@ -0,0 +1 @@
-e .[dev]
+1
View File
@@ -0,0 +1 @@
-e .
+11
View File
@@ -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"
+16
View File
@@ -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.
+16
View File
@@ -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>
);
}
+11
View File
@@ -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>
);
}
+8
View File
@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const config: NextConfig = {
output: "standalone",
transpilePackages: ["@aidoit/ui", "@aidoit/contracts", "@aidoit/sdk"],
};
export default config;
+28
View File
@@ -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"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "@aidoit/config/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
+218
View File
@@ -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;
}
}
+18
View File
@@ -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>
);
}
+91
View File
@@ -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>
);
}
+8
View File
@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const config: NextConfig = {
output: "standalone",
transpilePackages: ["@aidoit/ui", "@aidoit/contracts", "@aidoit/sdk"],
};
export default config;
+28
View File
@@ -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"
}
}
+11
View File
@@ -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"]
}
+49
View File
@@ -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
+18
View File
@@ -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
+36
View File
@@ -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
+66
View File
@@ -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:
+10
View File
@@ -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.
+23
View File
@@ -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"
}
}
+15
View File
@@ -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
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }]
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "@aidoit/config",
"version": "0.1.0",
"private": true,
"files": [
"*.json"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}
+19
View File
@@ -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"
}
}
+15
View File
@@ -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;
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "@aidoit/config/base.json",
"include": ["src"]
}
+22
View File
@@ -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"
}
}
+36
View File
@@ -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),
});
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "@aidoit/config/base.json",
"include": ["src"]
}
+24
View File
@@ -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"
}
}
+1
View File
@@ -0,0 +1 @@
export { StatCard } from "./stat-card";
+15
View File
@@ -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>
);
}
+52
View File
@@ -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;
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "@aidoit/config/react-library.json",
"include": ["src"]
}
+4
View File
@@ -0,0 +1,4 @@
packages:
- apps/web
- apps/msp
- packages/*
+24
View File
@@ -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/**"]
}
}
}