# Development Guide ## Prerequisites - **Node.js** ≥ 20 and **pnpm** ≥ 9 - **Python** ≥ 3.11 with `venv` support - **Docker** and **Docker Compose** (for local services) ## Installation ```bash # Install Node dependencies and Python virtualenv + packages make install # Or manually: pnpm install cd apps/api && python3 -m venv .venv && .venv/bin/pip install -e ".[dev]" ``` ## Environment Setup Copy the root environment example and fill in local values: ```bash cp .env.example .env ``` Copy the frontend environment example: ```bash cp apps/web/.env.example apps/web/.env ``` ### Frontend Authentication (OIDC) The frontend uses OpenID Connect (OIDC) with PKCE for authentication. Configure the following environment variables in `apps/web/.env`: | Variable | Description | Example | |----------|-------------|---------| | `VITE_API_URL` | Backend API base URL | `http://localhost:8000` | | `VITE_OIDC_ISSUER` | OIDC provider issuer URL | `https://authentik.example.com/application/o/headquarter` | | `VITE_OIDC_CLIENT_ID` | OIDC client ID | `headquarter-web` | | `VITE_OIDC_REDIRECT_URI` | Post-login redirect URL | `http://localhost:5173/callback` | **Authentication Flow:** 1. User clicks login → redirected to OIDC provider authorize endpoint 2. User authenticates with provider 3. Provider redirects to `/callback` with authorization code 4. Frontend exchanges code for access token (PKCE) 5. Token stored in `localStorage`, user info fetched from `/api/v1/users/me` **Logout:** - Clears local token - Redirects to login page - User can re-authenticate via OIDC flow ## Running Locally ### Frontend only ```bash cd apps/web pnpm dev # Vite dev server on http://localhost:5173 ``` ### Backend only ```bash cd apps/api .venv/bin/uvicorn app.main:app --reload --port 8000 ``` ### Both (via root script) ```bash pnpm dev # Runs frontend and backend in parallel ``` ### With Docker Compose ```bash docker compose up --build -d ``` ### Alembic Migrations Generate a new migration after modifying models: ```bash cd apps/api .venv/bin/alembic revision --autogenerate -m "description" ``` Apply migrations: ```bash cd apps/api .venv/bin/alembic upgrade head ``` Downgrade one revision: ```bash cd apps/api .venv/bin/alembic downgrade -1 ``` Or use the Makefile targets: ```bash cd apps/api make revision msg="description" make upgrade make downgrade ``` ## Testing ### Frontend ```bash pnpm --filter @headquarter/web test ``` Uses **Vitest** + **Testing Library** + **jsdom**. ### Backend ```bash pnpm --filter @headquarter/api test ``` Or directly with pytest: ```bash cd apps/api && .venv/bin/pytest ``` ### All tests ```bash make test # or pnpm test ``` ## CI / Testing A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push and pull request to `main`. It executes two jobs in parallel: - **web-ci** — checks out the repo, installs Node.js ≥ 20 and pnpm, then runs `lint`, `typecheck`, and `test` for `@headquarter/web`. - **api-ci** — checks out the repo, sets up Python 3.11, installs API dev dependencies (`pytest`, `ruff`, `mypy`, `httpx`), starts a PostgreSQL service container, then runs `ruff check`, `mypy`, and `pytest` for the API. The workflow reports pass/fail status directly on pull requests as required status checks. All commands must pass before a PR can be merged. ## Linting and Type Checking ### Frontend ```bash pnpm --filter @headquarter/web lint pnpm --filter @headquarter/web typecheck ``` ### Backend ```bash pnpm --filter @headquarter/api lint pnpm --filter @headquarter/api typecheck ``` ### All ```bash make lint make typecheck ``` ## Building ```bash make build # or pnpm build ``` ## Project Layout ```text ├── apps/ │ ├── web/ # Vite React TypeScript frontend │ └── api/ # FastAPI Python backend ├── packages/ # Shared packages (future) ├── docs/ # Documentation ├── deploy/ # Deployment skeleton files ├── docker-compose.yml └── package.json # Root monorepo scripts ``` ## Conventions - **Frontend**: React functional components, TypeScript strict mode, ESLint + Ruff-like rules. - **Backend**: FastAPI, Pydantic settings, pytest, ruff, mypy. - **Commits**: Conventional commits with task ID prefix, e.g. `feat(FN-002): description`. ## Git Abstraction The `app/git/` package in the backend provides provider-independent Git orchestration. It is split into two layers so that remote provider API logic and local CLI operations evolve independently: | Module | Responsibility | |--------|--------------| | `types` | Enumerations (`ProviderKind`, `CredentialKind`, `ConnectionStatus`, `SshKeyStatus`) | | `provider` | Abstract `GitProvider` — remote operations (`validate_connection`, `list_repositories`, `create_deploy_key`, …) | | `credentials` | `GitCredential` / `AccessTokenCredential` models and `CredentialStorage` ABC | | `ssh_key` | `SshKeyPair` model and `SshKeyLifecycle` (Ed25519 generation via `cryptography`) | | `connection` | `RepositoryConnection` ORM mapping and `ConnectionManager` orchestration | | `operations` | Abstract `GitOperations` and concrete `LocalGitOperations` (subprocess-based `get_status`) | Security rules for the package: - Credential models store **only** `encrypted_payload` — no plaintext `token` or `private_key` fields. - SSH private keys are encrypted before storage; the field uses `repr=False`. - Real encryption of the payload is deferred to FN-009; the current placeholder is base64-only. ## Tool Spawn Workflow The platform supports spawning development tools (e.g., code-server) as Docker containers via Docker Compose. ### Architecture 1. **Tool Manifest** (`apps/api/app/tools/manifests/*.yml`): - Defines Docker image, ports, volumes, environment variables, health checks - Loaded into in-memory registry at application startup 2. **Spawn Service** (`apps/api/app/services/spawn.py`): - Generates Docker Compose service definitions from manifests - Handles container lifecycle: spawn, stop, status polling - Integrates Traefik label generation for subdomain routing 3. **API Endpoints** (`apps/api/app/routers/tool_instances.py`): - `POST /projects/{id}/tool-instances` — Spawn a new tool instance - `POST /projects/{id}/tool-instances/{id}/stop` — Stop a running instance - `POST /projects/{id}/tool-instances/{id}/start` — Restart a stopped instance - `GET /projects/{id}/tool-instances/{id}/status` — Get container status 4. **Frontend UI**: - `/tools/spawn` — Form to select tool, project, and spawn - `/projects/{id}/instances/{id}` — Instance detail with status, controls, and "Open Tool" link ### Auth Proxy Spawned tools are protected behind Traefik forwardAuth middleware: - Traefik forwards requests to `/api/v1/auth/validate` for session validation - code-server built-in auth is disabled (`PASSWORD: ""`) - Only authenticated platform users can access spawned tools ### Local Development Ensure Docker socket is accessible and the `tools` network exists: ```bash docker network create tools # One-time setup ``` Spawned containers use the `tools` network for Traefik routing. ## Config & Secrets ### Overview The platform supports scoped configuration values and encrypted secrets that are injected into tool containers at spawn time. ### Scopes Configs and secrets support four scope levels (closest match wins): 1. **Global** — Available to all users and projects 2. **User** — Available to a specific user across all projects 3. **Project** — Available within a specific project 4. **Instance** — Available to a specific tool instance ### Configs Configs are plaintext JSON values mounted as files into containers: - Mount path: `/app/config/.json` - Permissions: `0400` (read-only, owner-only) - Scope resolution: instance > project > user > global **API Endpoints:** - `POST /configs` — Create config - `GET /configs` — List configs (filter by scope_type, scope_id) - `PUT /configs/{id}` — Update config value - `DELETE /configs/{id}` — Delete config **Frontend:** - `/projects/{id}/configs` — Config management UI ### Secrets Secrets are encrypted with Fernet and injected as environment variables: - Env var format: `=` - Values are never sent to the frontend decrypted (displayed as `••••••`) - Scope resolution: instance > project > user > global **API Endpoints:** - `POST /secrets` — Create secret - `GET /secrets` — List secrets (filter by scope_type, scope_id) - `PUT /secrets/{id}` — Update secret value - `DELETE /secrets/{id}` — Delete secret **Frontend:** - `/projects/{id}/secrets` — Secret management UI ### Runtime Injection When a tool instance is spawned: 1. Configs are resolved from all applicable scopes 2. Secrets are resolved and decrypted 3. Config files are generated in `/tmp/headquarter-configs/{instance_id}/` 4. Config files are mounted as read-only volumes 5. Secrets are injected as environment variables 6. Missing required secrets will fail the spawn with a clear error ### Validation Before spawning, the system validates that all required secrets exist. If any are missing, the spawn fails with an error message listing the missing secrets. ## OpenCode Tool ### Overview OpenCode is an AI-powered terminal-based development environment accessible via web browser. It is included as a built-in tool manifest alongside code-server. ### Manifest **File:** `apps/api/app/tools/manifests/opencode.yml` ```yaml id: opencode name: OpenCode image: ghcr.io/opencode-ai/opencode:latest ports: - container_port: 3000 primary: true ``` ### Web Terminal Access OpenCode exposes a terminal interface on port 3000: - **Subdomain:** `opencode-{project}-{user}.{domain}` - **Health Check:** `GET /` on port 3000 - **Terminal:** Full xterm-256color support with color output ### Environment Variables The following environment variables are configured for terminal support: | Variable | Value | Description | |----------|-------|-------------| | `TERM` | `xterm-256color` | Terminal type with color support | | `FORCE_COLOR` | `"1"` | Force color output | ### Workspace Mount OpenCode mounts the project workspace at `/workspace` for persistent file access. ### Config Mount User-specific OpenCode configuration is mounted at `/root/.config/opencode`. ### Usage 1. Navigate to `/tools/spawn` in the frontend 2. Select "OpenCode" from the tool dropdown 3. Choose a project 4. Enter an instance name 5. Click "Spawn Tool" 6. Once running, click "Open Tool" to access the web terminal ### Differences from code-server | Feature | OpenCode | code-server | |---------|----------|-------------| | Interface | Terminal (web-based) | VS Code (web-based) | | Port | 3000 | 8080 | | Primary Use | Terminal/CLI tasks | Code editing/IDE | | AI Features | Built-in AI assistance | Extensions required | ### Local Testing To test OpenCode locally without the full platform: ```bash docker run -it --rm \ -p 3000:3000 \ -e TERM=xterm-256color \ -e FORCE_COLOR=1 \ ghcr.io/opencode-ai/opencode:latest ``` Then open `http://localhost:3000` in your browser.