Monorepo layout: apps/web (React frontend), apps/server (Express API), packages/ (shared types and utilities). Key config files and what lives where.
The Paperclip monorepo layout, what each directory contains, and where to look when you want to change or extend specific behavior.
paperclip/
├── apps/
│ ├── server/ ← Express API + heartbeat scheduler
│ └── web/ ← React 19 + Vite frontend
├── packages/
│ └── shared/ ← TypeScript types shared between server and web
├── .env.example ← environment variable template
├── .env ← your local config (git-ignored)
├── docker-compose.yml ← container definitions for Docker path
├── package.json ← workspace root (pnpm workspaces)
└── pnpm-workspace.yaml ← declares apps/* and packages/* as workspaces
apps/server/
├── src/
│ ├── index.ts ← entry point: creates Express app, starts scheduler
│ ├── routes/ ← one router file per entity
│ │ ├── companies.ts ← GET/POST/PATCH/DELETE /api/companies
│ │ ├── agents.ts ← /api/agents
│ │ ├── issues.ts ← /api/issues
│ │ ├── heartbeat.ts ← /api/heartbeat/trigger, /api/heartbeat/runs
│ │ ├── approvals.ts ← /api/approvals
│ │ └── activity.ts ← /api/activity
│ ├── services/
│ │ ├── heartbeat.service.ts ← core scheduler: selects agents, invokes adapters
│ │ ├── cost.service.ts ← records cost_events, checks budgets
│ │ └── approval.service.ts ← creates and resolves approval gates
│ ├── adapters/
│ │ ├── base.adapter.ts ← the Adapter interface definition
│ │ ├── claude.adapter.ts ← Claude Code CLI adapter
│ │ ├── openai.adapter.ts ← OpenAI chat completions adapter
│ │ ├── http.adapter.ts ← HTTP webhook adapter
│ │ └── shell.adapter.ts ← shell process adapter
│ └── db/
│ ├── schema.ts ← Drizzle ORM schema (all 7 tables)
│ ├── migrate.ts ← runs migrations on startup
│ └── migrations/ ← SQL migration files (auto-generated)
├── package.json
└── tsconfig.json
The most important file is heartbeat.service.ts — it contains the core execution loop that the entire platform runs on.
apps/web/
├── src/
│ ├── main.tsx ← React entry point
│ ├── App.tsx ← router setup (React Router v6)
│ ├── pages/
│ │ ├── Dashboard.tsx ← company overview, agent cards
│ │ ├── Agents.tsx ← agent list, hire/edit/pause
│ │ ├── Issues.tsx ← issue board (Kanban)
│ │ ├── Runs.tsx ← heartbeat run history + costs
│ │ └── Approvals.tsx ← pending approvals inbox
│ ├── components/ ← reusable UI components
│ │ ├── AgentCard.tsx
│ │ ├── IssueRow.tsx
│ │ ├── CostChart.tsx
│ │ └── ApprovalGate.tsx
│ └── api/
│ └── client.ts ← typed fetch wrapper for every endpoint
├── package.json
└── vite.config.ts
packages/shared/
└── src/
├── types/
│ ├── company.types.ts
│ ├── agent.types.ts
│ ├── issue.types.ts
│ └── heartbeat.types.ts
└── index.ts ← re-exports everything
Both apps/server and apps/web import from @paperclip/shared. This ensures the TypeScript types for API request/response shapes are identical on both ends — no silent type drift.
| Task | File to edit |
|---|---|
| Add a new API endpoint | apps/server/src/routes/ |
| Change task selection logic | apps/server/src/services/heartbeat.service.ts |
| Add a new adapter type | apps/server/src/adapters/ |
| Change the DB schema | apps/server/src/db/schema.ts + run pnpm db:generate |
| Add a new dashboard page | apps/web/src/pages/ + register in App.tsx |
| Add a shared type | packages/shared/src/types/ |
packages/shared?