Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md

Guidance for AI coding agents working in this repository. **Postmill** is an open-source, AI-native
platform to schedule social media and chat posts to **45+ channels** — schedule posts, calendar view,
persisted analytics, team management, and a media library. Posts added to the calendar enter a
workflow and are published at the right time.

This file is the entry point (`CLAUDE.md` imports it). It carries only the load-bearing rules. The
agent development docs live next to it:

- **[`ARCHITECTURE.md`](./ARCHITECTURE.md)** — system architecture: layout, request lifecycle,
  provider kernel, data flows, identity/RBAC/sessions.
- **[`agents/`](./agents/README.md)** — the agent development docs: provider how-tos for all 11
  domains, UI standards, libraries map, backend/frontend recipes, database, testing, security, jobs,
  and subsystem deep-dives. **Read the doc for your task before writing code** — the index in
  [`agents/README.md`](./agents/README.md) maps tasks to docs.
- **[`agents/skills/`](./agents/skills/)** — executable skills (`<skill-name>/SKILL.md`): task
  procedures that wrap the docs (add a provider, new endpoint, schema change, new UI, …). When a
  request matches a skill, follow it.

`docs/` (the VitePress site) is documentation for **humans** — end users and self-hosters. It is not
agent development guidance; do not follow pointers there for development work. When you change a
convention, contract, or invariant, update the matching `agents/` doc in the same change.

## Version

The current release version is tracked in [`version.txt`](./version.txt) (now `v1.4.0`). **Bump it on
every release.** Root docs speak only of `v1.0.0` as the first public release; the inherited 3.x/4.x
numbering was pre-release internal development. An app release is a `vX.Y.Z` tag: `build-containers.yml`
refuses to publish a container image whose tag does not match `version.txt`, then creates the GitHub
Release (notes + image digest + `docker save` tarball).

Two artifacts version **independently** of the product release: `apps/sdk` (the published
`@postmill-ai/postmill-sdk` npm package) and `apps/extension` (the browser extension, published to
the Chrome Web Store). Do not "align" them to `version.txt`; bump each per its own semver when that
artifact changes. An SDK release is an `sdk-vX.Y.Z` tag: `publish-sdk.yml` refuses a tag that does
not match `apps/sdk/package.json`, publishes to npm (OIDC trusted publishing — no token secret) and
GitHub Packages, and creates its own GitHub Release. Maintainer runbook: `CONTRIBUTING.md` § Releasing.

> **This system is in production with many users.** Before changing anything, be sure you are not
> breaking existing users — a data/schema change may need a migration story. Prefer
> backward-compatible changes.

## Repository layout

PNPM monorepo. Workspaces are driven by `pnpm --filter`. Dependencies are split between the root
`package.json` (shared tooling and cross-cutting packages) and per-workspace `package.json` files in
`apps/*` and `libraries/*` (feature-specific packages). Do not add a backend-only or frontend-only
package to the root manifest unless it is genuinely shared across multiple workspaces.

- `apps/backend` — NestJS REST API. Kept **thin**: controllers + module wiring. Real logic lives in
  libraries. Serves the Inngest handler at `/api/inngest`.
- `apps/frontend` — **Next.js (App Router) + React**, port `4200`. Tailwind 4, Sentry-instrumented.
- `apps/extension` — browser extension. `apps/commands` — CLI commands. `apps/sdk` — published SDK.
- `libraries/nestjs-libraries` — the bulk of shared server logic, Prisma schema, repositories. **Most
  backend logic belongs here.**
- `libraries/helpers` — shared utilities, incl. the `useFetch` hook. `libraries/react-shared-libraries`
  — shared React components (imported as `@postmill-ai/react/*`).
- `libraries/providers` — the unified provider framework (kernel + one package per provider).
- `tools/` — repo-owned tooling, **not** a workspace. `tools/db` (migration/backfill helpers, incl.
  the two CI schema gates) and `tools/codegen` (generators whose output is committed). Anything CI
  or a documented runbook invokes belongs here.
- `docker/` — every Docker artifact except the three that must sit at the root: `Dockerfile` (the
  image CI publishes), `docker-compose.yaml` (the self-hoster quick-start, whose directory
  determines the Compose project name and therefore the volume prefix), and `.dockerignore` (read
  from the build-context root).
- `e2e/` — Playwright suite and its seed data. `agents/`, `docs/` — see the doc map below.

> `scripts/` at the repo root is **maintainer-local and gitignored**. Never add anything CI, a
> workspace script, or a doc depends on — that goes in `tools/`.

Full directory tour, import aliases, and "where does new code go": [`agents/libraries.md`](./agents/libraries.md).

## Doc map

| Task | Read |
|---|---|
| Understand the system | [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
| Add a provider (AI, social, media, storage, short-link, VPN, content pack, email, auth) | [`agents/providers/overview.md`](./agents/providers/overview.md) → the domain doc |
| Write any frontend UI | [`agents/ui-standards.md`](./agents/ui-standards.md) + [`agents/frontend.md`](./agents/frontend.md) |
| Add an endpoint / DTO / repository / job | [`agents/backend.md`](./agents/backend.md) (+ [`agents/jobs.md`](./agents/jobs.md) for Inngest) |
| Change the schema | [`agents/database.md`](./agents/database.md) |
| Write tests / run lint | [`agents/testing.md`](./agents/testing.md) |
| Touch auth, secrets, outbound HTTP | [`agents/security.md`](./agents/security.md) |
| Send a notification | [`agents/notifications.md`](./agents/notifications.md) |
| Campaigns / billing / video rendering | [`agents/campaigns.md`](./agents/campaigns.md) / [`agents/billing.md`](./agents/billing.md) / [`agents/video-rendering.md`](./agents/video-rendering.md) |

## Unified provider framework

All provider domains (AI, Media, Storage, Short-link, Social, VPN, Content Packs, Email, Auth, Comms, Payments) resolve
through a single **`ProviderKernel`** (`libraries/providers/kernel`), one workspace package per
provider (`libraries/providers/<id>`), each version an internal module (`src/v1`, `src/v2`, …).

- A provider is addressed as `domain/providerId@version` (e.g. `ai/openai@v1`). Every config/ledger
  row carries a non-null `version` column and keeps that version until an explicit upgrade. Lifecycle:
  `preview → active → deprecated` (rejects new writes) `→ retired` (returns `410 Gone`).
- **Resolution is through `ProviderResolutionService` — the kernel is the SOLE resolution path.** The
  legacy in-memory registries and the `PROVIDER_KERNEL=legacy` kill switch were **removed**; do not
  reference them as live.
- API: `GET /providers/catalog?domain=` (**authenticated**; unknown `?domain=` → **400**);
  `GET /admin/providers/health?domain=` (super-admin).
- Free stock providers (Unsplash, Pexels, Pixabay, GIPHY, Jamendo, Iconify) are intentionally outside
  versioning — no stored config row.

How to add a provider in any domain: [`agents/providers/overview.md`](./agents/providers/overview.md).

## Setup & commands

Use **pnpm only** — never npm or yarn.

```bash
pnpm install              # also runs prisma-generate via postinstall

# Develop (all apps in parallel)
pnpm run dev              # extension + backend + frontend
pnpm run dev:minimal      # backend + frontend only (recommended for daily dev)
pnpm run dev:backend      # backend only
pnpm run dev:frontend     # frontend only (port 4200)

# Build
pnpm run build            # frontend + backend

# Test (Vitest, per package)
pnpm run test             # helpers → providers → nestjs-libraries → backend → frontend → commands → sdk → extension
vitest run --root apps/backend            # run one package's tests

# Database (Prisma 6.5.0)
pnpm run prisma-generate  # regenerate client after editing schema.prisma
pnpm run prisma-db-push   # push schema to the DB (local prototyping/reset only)

# Generated artifacts (committed; CI fails the build if they drift)
pnpm run build:backend && pnpm run openapi:generate   # refresh openapi.yml after a route/DTO change
node tools/codegen/generate-studio-descriptor-registry.mjs   # refresh provider metadata
```

**`openapi.yml` is generated — do not hand-edit it.** It is built from the controllers, so a route
or DTO change means re-running the generator and committing the result. It needs the backend built
plus `DATABASE_URL`/`REDIS_URL`/`JWT_SECRET`, because producing the document constructs `AppModule`.
Enrich the output by adding `@ApiOperation`/`@Api*Response` decorators or JSDoc, never by editing
the YAML.

- **Tests run on Vitest** (`vitest run --root <pkg>`). The old root `jest.config.ts` has been
  deleted — do not resurrect jest-style configuration. Details: [`agents/testing.md`](./agents/testing.md).
- **Lint runs from the repo root only**, via the flat `eslint.config.mjs` (eslint 9 +
  `eslint-config-next`). There is no per-package `lint` script.

## Local development performance

The stack is large; use the feature flags and lightweight commands below to keep your machine
responsive.

```bash
# Required services only: postgres + redis
docker compose -f docker/docker-compose.dev.yaml up -d
# Add background jobs (Inngest dev server)
docker compose -f docker/docker-compose.dev.yaml --profile jobs up -d

# Skip heavy optional subsystems (all flags default to enabled)
DEV_DISABLE_AI=true DEV_DISABLE_MCP=true DEV_DISABLE_MEDIA=true \
DEV_DISABLE_SHORTLINKS=true DEV_DISABLE_EMAIL=true pnpm run dev:minimal
```

Flags: `DEV_DISABLE_AI`, `DEV_DISABLE_MCP`, `DEV_DISABLE_MEDIA`, `DEV_DISABLE_SHORTLINKS`,
`DEV_DISABLE_EMAIL`, `DEV_DISABLE_VIDEO`, `DEV_DISABLE_AGENT`, `DEV_DISABLE_CRON`,
`DEV_DISABLE_SENTRY`, `DEV_DISABLE_OPENTELEMETRY`, `DEV_DISABLE_COMMS`.

The backend dev script sets `--max-old-space-size=6144`. Frontend dev variants: `pnpm run
dev:frontend` (Turbopack), `pnpm run dev:webpack` (fallback), `pnpm run analyze` (bundle report).

## Golden rules

The one-paragraph version of the conventions — details and recipes in the linked docs.

- **Backend layering:** Controller → Service → Repository (→ Manager when involved). Only
  repositories touch Prisma. Logic lives in `libraries/nestjs-libraries`, not `apps/backend`.
  Sanctioned exceptions (seeders; `// layering: sanctioned leaf-read` sites) — keep them, don't
  "fix" them. Recipes and the full exception list: [`agents/backend.md`](./agents/backend.md).
- **Frontend:** bespoke primitives first — `Button`/`Input` from `@postmill-ai/react/form/*`,
  `useModals()` for modals; Mantine only for Autocomplete/dates/hooks; no new UI kits; no
  `--color-custom*` tokens; SWR via `useFetch`, one hook per resource, never eslint-disable a hook.
  The rulebook: [`agents/ui-standards.md`](./agents/ui-standards.md).
- **Database:** schema changes via committed migrations; `prisma migrate deploy` is the only apply
  path for shared/production DBs; new columns nullable or defaulted; destructive changes need
  expand-contract + `ALLOW_DESTRUCTIVE_SCHEMA=true`. Workflow: [`agents/database.md`](./agents/database.md).
- **Channel credentials:** per-org `OrgProviderConfiguration` (encrypted) always wins; the platform
  env OAuth-app fallback is live, per-request, never persisted, and **channels only** — AI,
  short-link, and other provider credentials do NOT get an env fallback. Resolution funnels through
  `IntegrationManager.getClientInformation`. Channels can opt into VPN-region proxy egress
  (SOCKS5/HTTP-CONNECT only).

### Numbers stated once (do not let them drift)

- **AI providers: 31** (17 direct + 14 hubs/gateways), BYOK, no env fallback.
- **Media tools: 46** = Designer + AI Designer + **38 provider studios** + **6 stock browsers**.
- **Channels: 45+.**
- **Background jobs: Inngest** (the previous workflow orchestrator was removed — there is **no
  `while(true)` poll loop and no `continueAsNew`**). Function catalog: [`agents/jobs.md`](./agents/jobs.md).
- **Notification categories: 10** — `post_published`, `post_failed`, `channels`, `comments`,
  `budget`, `media`, `announcements`, `streak`, `agent`, `analytics`. Hardcoded in three lockstep
  places (DTO, `DEFAULT_CATEGORY_TOGGLES`, frontend panel). `NotificationService` is the **single
  chokepoint** for user-facing email + in-app/push — never call `EmailService` directly from feature
  code. See [`agents/notifications.md`](./agents/notifications.md).

## Security invariants (do not break)

Condensed "don't break this" set. Full detail with enforcement points: [`agents/security.md`](./agents/security.md).

- **No env-`OPENAI_API_KEY` (or any env AI-key) fallback.** No active AI provider for an org ⇒ AI is
  **off** for that org across all four surfaces (utility, generator, agent, mcp). AI keys live in
  `AIOrgProviderConfig`, encrypted at rest.
- **The `ProviderKernel` is the sole resolution path** (see above).
- **All user-influenced outbound HTTP goes through `safeFetch`**
  (`libraries/nestjs-libraries/src/dtos/webhooks/safe.fetch.ts`). No bare `fetch(userUrl)`.
  `SSRF_ALLOWED_PRIVATE_CIDRS` is the opt-in for self-hosted instances.
- **Secrets at rest are encrypted via `EncryptionService`** (AES-GCM, `v2:` prefix). **Single-key
  model:** no per-org crypto key; cross-org isolation is enforced by query scoping. Per-org reads on
  `EncryptionService`, global reads on `AuthService.fixedEncryption` (same key, two routes — never
  cross them for one row). Never store secrets plaintext.
- **Never run blocking Redis (BRPOP/BLPOP/BRPOPLPUSH) on the shared `ioRedis` client.** Use
  `ioRedis.duplicate()`.
- **Inngest idempotency ids must be event-unique** — a constant id black-holes reschedules.
- **JWT** verification pins `algorithms: ['HS256']`; new tokens carry `exp` with sliding renewal;
  session-auth verify sites require `exp` (legacy exp-less session tokens rejected).
  IDs/secrets use CSPRNG.
- **CSRF is required on cookie-authenticated mutating routes** (controllers in the
  `authenticatedController` array get it automatically). The **global validation pipe rejects unknown
  fields** (`whitelist` + `forbidNonWhitelisted`) — declare new optional fields on their DTO.
- **Two orthogonal access gates:** billing (`@CheckPolicies` + `PoliciesGuard`) → 402; RBAC
  (`@RequirePermission` + `OrgRbacGuard`) → 403. `User.isSuperAdmin` bypasses RBAC, not billing.
- **Throttling is effective** (`ThrottlerBehindProxyGuard` throttles by default, `API_LIMIT` default
  600/h). CopilotKit `/chat` is policy- and budget-gated.
- **`NOT_SECURED` is a dev-only toggle** — with `NODE_ENV=development` it relaxes helmet/HSTS and
  cookie `Secure`/`sameSite` flags. Never set it in production.
- **No secrets/PII in Sentry, error storage, or logs** — the Sentry scrubber
  (`libraries/nestjs-libraries/src/sentry/initialize.sentry.ts`) is the backstop; capture is disabled
  at source.

## Identity, RBAC & sessions

- **Identity/profile split:** `User` keeps identity/auth columns; user-level profile fields (name, bio,
  pictureId, timezone, preferredLanguage, notification prefs) live on `UserProfile` (1:1), shared across organizations.
- **RBAC:** `AppRole` (org-scoped; NULL org = system template; `key` =
  owner/admin/editor/member/viewer; `isSystem` = seeded). `Permission` catalog: **18 resources × 5
  actions = 90** seeded. Gating: `@RequirePermission(resource, action)` + `OrgRbacGuard`.
- **Sessions & refresh tokens:** the `Session` model backs refresh-token rotation (login creates,
  refresh rotates `tokenHash`, reuse of a rotated hash revokes, logout sets `revokedAt`).
- **Platform auth providers:** `AuthProviderConfig` is managed by the **separate administration app**;
  this repo only *reads* it (DB-first, env-bootstrap fallback). `LOCAL` auth always available unless
  `DISABLE_REGISTRATION`; OIDC SSO via `Provider.GENERIC`.

Detail: [`ARCHITECTURE.md`](./ARCHITECTURE.md) § Identity, RBAC & sessions.

## Removed legacy subsystems

Do **not** resurrect these dropped Prisma models / code paths (they reference real deleted schema, not
branding). Removed: `SocialMediaAgency`, `MessagesGroup`, `Orders`, `OrderItems`, `PayoutProblems`,
`ItemUser`, `GitHub`, `Star`, `Trending`, `TrendingLog`, `Messages` + associated enums (`OrderStatus`,
`From`) and their relations. The legacy `Role` enum and `UserOrganization.role` column were dropped —
superseded by `AppRole`-based RBAC (`UserOrganization.roleId`). The legacy `/third-party` integration
subsystem (the `@ThirdParty` decorator, `ThirdPartyManager`, the `ThirdParty` Prisma model) was
deleted — AI avatar video now lives only in the modern HeyGen Studio. The previous workflow
orchestrator was replaced by Inngest. The legacy global provider-config tables `AIProviderConfig`
and `ProviderConfiguration` were dropped in v1.0.0 (migration `20260830180000_no_legacy_v1`) along
with the global `AISystemSettings` selection columns (`activeProvider`/`activeModel`/`scopeModels`/
`rateLimitSettings`) and the deprecated `UserProfile.send*Emails` toggles — per-tenant
`AIOrgProviderConfig` / `OrgProviderConfiguration` and `NotificationPreference.categories` are the
only stores.