# Technology Stack

**Analysis Date:** 2026-03-30
**Project Name:** Paperclip (package name `paperclip`, npm org `@paperclipai`)

---

## Languages

**Primary:**
- TypeScript 5.7.x — all source code across every package, compiled to ESM
- JavaScript (ESM) — build scripts, generated output

**Secondary:**
- Bash — release scripts, smoke tests, DB backup (`scripts/`, `tests/`)

---

## Runtime

**Environment:**
- Node.js >=20 (enforced via `engines` in root `package.json`)
- ESM-first: all packages set `"type": "module"`
- `tsx` (^4.19.2) used as TS runner during development and for CLI execution

**Package Manager:**
- pnpm 9.15.4 (pinned via `packageManager` field)
- Lockfile: `pnpm-lock.yaml` — present and committed
- Patched dependency: `embedded-postgres@18.1.0-beta.16` (patch in `patches/`)

---

## Monorepo Structure

**Workspace layout** (`pnpm-workspace.yaml`):
```
packages/*
packages/adapters/*
packages/plugins/*
packages/plugins/examples/*
server
ui
cli
```

**Packages:**

| Package | Name | Purpose |
|---------|------|---------|
| `server/` | `@paperclipai/server` | Express HTTP server + WebSocket + plugin host |
| `ui/` | `@paperclipai/ui` | React SPA (board UI) |
| `cli/` | `paperclipai` | CLI binary (Node.js, esbuild-bundled) |
| `packages/db/` | `@paperclipai/db` | Drizzle ORM schema, migrations, embedded-postgres helpers |
| `packages/shared/` | `@paperclipai/shared` | Shared Zod schemas and TypeScript types |
| `packages/adapter-utils/` | `@paperclipai/adapter-utils` | Shared utilities across AI adapters |
| `packages/adapters/claude-local/` | `@paperclipai/adapter-claude-local` | Adapter for Anthropic Claude Code CLI |
| `packages/adapters/codex-local/` | `@paperclipai/adapter-codex-local` | Adapter for OpenAI Codex CLI |
| `packages/adapters/cursor-local/` | `@paperclipai/adapter-cursor-local` | Adapter for Cursor editor agent |
| `packages/adapters/gemini-local/` | `@paperclipai/adapter-gemini-local` | Adapter for Google Gemini CLI |
| `packages/adapters/openclaw-gateway/` | `@paperclipai/adapter-openclaw-gateway` | Gateway adapter (WebSocket, external agent relay) |
| `packages/adapters/opencode-local/` | `@paperclipai/adapter-opencode-local` | Adapter for opencode-ai CLI |
| `packages/adapters/pi-local/` | `@paperclipai/adapter-pi-local` | Adapter for pi.ai |
| `packages/plugins/sdk/` | `@paperclipai/plugin-sdk` | Public plugin API (worker-side + UI bridge hooks) |

---

## Backend Framework

**HTTP Server:**
- Express 5.1.0 (`express`) — REST API, static file serving, middleware chain
- `@types/express` 5.0.0

**WebSocket (Realtime):**
- `ws` ^8.18.0 — native WebSocket server at `server/src/realtime/live-events-ws.ts`
- Used for live event streaming to the board UI

**Authentication:**
- `better-auth` 1.4.18 — pluggable auth library with Drizzle adapter
- Session handling at `server/src/auth/better-auth.ts`
- JWT used for agent-to-server auth (`server/src/agent-auth-jwt.ts`)

**Validation:**
- `zod` ^3.24.2 — schema validation throughout server and shared packages
- `ajv` ^8.18.0 + `ajv-formats` ^3.0.1 — JSON Schema validation (plugin manifests)

**Logging:**
- `pino` ^9.6.0 — structured JSON logging
- `pino-http` ^10.4.0 — HTTP request logging middleware
- `pino-pretty` ^13.1.3 — dev-mode pretty-print

**File Handling:**
- `multer` ^2.0.2 — multipart/form-data upload handling
- `sharp` ^0.34.5 — server-side image processing

**HTML Sanitization:**
- `dompurify` ^3.3.2 + `jsdom` ^28.1.0 — server-side sanitization of rich text

---

## Database

**ORM:**
- `drizzle-orm` ^0.38.4 — TypeScript ORM, query builder
- `drizzle-kit` ^0.31.9 — migration generation (`drizzle-kit generate`)

**Driver:**
- `postgres` ^3.4.5 — native PostgreSQL client

**Database Server:**
- PostgreSQL 17 (Docker: `postgres:17-alpine`)
- `embedded-postgres` ^18.1.0-beta.16 — bundled PostgreSQL for local/single-binary deployments (patched)

**Schema location:** `packages/db/src/schema/` — 50+ individual table files
**Migrations:** `packages/db/src/migrations/`
**Client factory:** `packages/db/src/client.ts` (`createDb(url)`)

**Database modes:**
- `embedded-postgres` — default for local CLI use (no external DB required)
- `postgres` — external PostgreSQL for Docker/production deployments

---

## Frontend Framework

**Framework:**
- React 19.0.0 (`react`, `react-dom`)
- React Router DOM 7.1.5 (`react-router-dom`) — SPA routing
- React Query / TanStack Query 5.x (`@tanstack/react-query`) — server state, data fetching

**Build Tool:**
- Vite 6.1.0 — dev server (port 5173) and production bundler
- `@vitejs/plugin-react` ^4.3.4 — JSX/Fast Refresh
- In dev mode, Vite runs as Express middleware via `vite.middlewares` integration

**Styling:**
- Tailwind CSS 4.0.7 — utility-first CSS
- `@tailwindcss/vite` ^4.0.7 — Vite plugin
- `@tailwindcss/typography` ^0.5.19 — prose styles
- `tailwind-merge` ^3.0+ — conditional class merging
- `class-variance-authority` ^0.7.1 — component variant management
- `clsx` ^2.1.1 — conditional class names

**UI Components:**
- `radix-ui` ^1.4.3 — unstyled accessible primitives
- `@radix-ui/react-slot` ^1.2.4
- Component files in `ui/src/components/ui/`: button, card, dialog, input, badge, tabs, tooltip, etc. (shadcn-style pattern)
- `lucide-react` ^0.574.0 — icon library
- `cmdk` ^1.1.1 — command palette

**Rich Text / Markdown:**
- `@mdxeditor/editor` ^3.52.4 — rich markdown editor component
- `lexical` 0.35.0 + `@lexical/link` — editor framework (peer dep of MDXEditor)
- `react-markdown` ^10.1.0 — Markdown rendering
- `remark-gfm` ^4.0.1 — GitHub-flavored Markdown
- `mermaid` ^11.12.0 — diagram rendering

**Drag-and-Drop:**
- `@dnd-kit/core` ^6.3.1, `@dnd-kit/sortable` ^10.0.0, `@dnd-kit/utilities` ^3.2.2

**Path Alias:** `@/` maps to `ui/src/` (configured in `vite.config.ts`)

---

## CLI

**Framework:**
- `commander` ^13.1.0 — command parsing
- `@clack/prompts` ^0.10.0 — interactive terminal prompts
- `picocolors` ^1.1.1 — terminal color output

**Build:**
- `esbuild` ^0.27.3 — bundles CLI to single `dist/index.js` (config: `cli/esbuild.config.mjs`)

---

## Storage

**Providers (runtime-selectable):**
- Local disk — `server/src/storage/local-disk-provider.ts` — default, stores under `PAPERCLIP_HOME`
- AWS S3 — `server/src/storage/s3-provider.ts` — via `@aws-sdk/client-s3` ^3.888.0

**Secrets:**
- Local encrypted provider — `server/src/secrets/local-encrypted-provider.ts`
- External stub providers — `server/src/secrets/external-stub-providers.ts`

---

## Testing Frameworks

**Unit / Integration:**
- Vitest ^3.0.5 — test runner (configured in root `vitest.config.ts` as multi-project)
- Projects under test: `packages/db`, `packages/adapters/opencode-local`, `server`, `ui`, `cli`
- Server tests: `server/src/__tests__/` (~95 test files)
- Server vitest config: `server/vitest.config.ts` (env: `node`)
- `supertest` ^7.0.0 — HTTP integration testing for Express routes

**E2E:**
- Playwright ^1.58.2 — browser E2E tests
- Config: `tests/e2e/playwright.config.ts`
- Browser: Chromium only
- `tests/e2e/` — feature specs, `tests/release-smoke/` — release smoke tests

**Evaluations:**
- `promptfoo` 0.103.3 — LLM prompt evaluation (`evals/promptfoo/`)

---

## Build and Tooling

**TypeScript:**
- TypeScript 5.7.3 across all packages
- Base config: `tsconfig.base.json` — target ES2023, `NodeNext` module resolution, strict mode
- Each package extends base or defines its own `tsconfig.json`

**Dev Runner:**
- `scripts/dev-runner.mjs` — coordinates parallel dev processes (server + UI)
- `chokidar` ^4.0.3 — file watching in server dev mode

**CLI published as:** `paperclipai` on npm (binary: `dist/index.js`)
**Server published as:** `@paperclipai/server` on npm
**Plugin SDK published as:** `@paperclipai/plugin-sdk` on npm

---

## Docker / Deployment

**Base image:** `node:lts-trixie-slim` (Debian-based)

**Multi-stage Dockerfile:**
1. `base` — Node + pnpm + ca-certificates + curl + git
2. `deps` — install all dependencies with frozen lockfile
3. `build` — build UI, plugin-sdk, server in order
4. `production` — copy build output; globally install `@anthropic-ai/claude-code`, `@openai/codex`, `opencode-ai`

**Port:** 3100 (configurable via `PORT` env var)
**Data volume:** `/paperclip` — all persistent state (DB, uploads, config)

**Compose variants:**
- `docker-compose.yml` — full stack with external Postgres 17
- `docker-compose.quickstart.yml` — single container, embedded Postgres
- `docker-compose.untrusted-review.yml` — special security sandbox mode

**Key env vars:**
- `DATABASE_URL` — external PostgreSQL URL (omit for embedded mode)
- `BETTER_AUTH_SECRET` — required auth secret
- `PAPERCLIP_DEPLOYMENT_MODE` — `authenticated` | `unauthenticated`
- `PAPERCLIP_DEPLOYMENT_EXPOSURE` — `private` | `public`
- `PAPERCLIP_PUBLIC_URL` — public-facing URL
- `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` — AI provider keys
- `PAPERCLIP_HOME` — data root directory (default `/paperclip` in Docker)

---

## AI Agent Integrations (Adapters)

Each adapter follows a consistent three-export pattern: `./server`, `./ui`, `./cli`.

| Adapter | Target Agent CLI |
|---------|-----------------|
| `adapter-claude-local` | `@anthropic-ai/claude-code` |
| `adapter-codex-local` | `@openai/codex` |
| `adapter-cursor-local` | Cursor editor |
| `adapter-gemini-local` | Gemini CLI |
| `adapter-openclaw-gateway` | Remote agent relay (WebSocket, `ws`) |
| `adapter-opencode-local` | `opencode-ai` |
| `adapter-pi-local` | pi.ai |

The `hermes-paperclip-adapter` 0.1.1 is an additional server-side dependency (third-party adapter protocol).

---

*Stack analysis: 2026-03-30*
