Reading the repo
Opening the box…
Reading the repo
Opening the box…
How to install on Linux
Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.
Fresh setup, read just now
Will it work on Linux?
Heads-upBuilding it yourself? It also needs PostgreSQL and 6 settings in .env (1 secret).
docs/setup.mdOperational detail moved out of the rule docs. api.md, agent.md and
environment.md are what agents read before changing code; this is what a person
reads once.
cp .env.example .env # fill DATABASE_URL, BETTER_AUTH_SECRET, ALLOWED_SIGN_IN
docker compose up -d # Postgres, matching .env.example
bun run db:migrate && bun run db:seed
bun run dev # app :3000, api :3001, agent :2000
Prisma from the repo root: db:generate, db:migrate, db:push, db:reset,
db:seed, db:studio, db:deploy.
dev depends on ^dev:prepare, so every start applies pending migrations and
regenerates the Prisma client before a single server boots. That is why the first
run needs db:migrate only for the seed that follows it. When the database and
schema.prisma have diverged past what migrate deploy can reconcile,
dev:prepare stops the whole run rather than starting servers against a schema
they do not match — reconcile with db:migrate, or db:reset when the divergence
is an edited migration that has already been applied.
gmail.readonly is a restricted scope, so an External app needs OAuth
verification plus an annual CASA assessment. Going External later means the full
review — a decision, not a checkbox.AGENT_URL="http://127.0.0.1:2000" # 127.0.0.1, not localhost: eve dev is IPv4-only
AGENT_BRIDGE_SECRET="$(openssl rand -base64 32)"
| Agent tab error | Cause |
| --- | --- |
| 503 | AGENT_BRIDGE_SECRET unset in the app's process |
| 401 | The two processes hold different secrets, or passThroughEnv in apps/app/turbo.json / apps/agent/turbo.json is missing the pair (Turbo is strict-env) |
| 502 | Agent not running, or AGENT_URL wrong |
localDev() accepts any loopback request, so curl 127.0.0.1 proves nothing about
the bridge — send -H 'Host: agent.example.com'. GET /eve/v1/info is the whole
inventory, including a diagnostics count that finds files eve silently ignored.
The agent package's default dev command is interactive eve dev. The root
Turbo task marks it interactive, so select the agent pane and press Enter before
using the eve TUI. Run turbo run dev:headless --filter=agent when a terminal
cannot render the TUI; that uses eve dev --no-ui, and the Turbo pane is the
record because only interactive development writes .eve/logs/ for eve logs.
Reach for the Turbo task rather than bun run --filter=agent dev:headless: the
package script alone skips dev:prepare, so the agent would start against
unmigrated tables.
hooks/activity.ts is the replacement narration, to stderr (the TUI hides
stdout), printing shape everywhere and argument contents outside production only. It
is not the audit trail — hooks/audit.ts writes AgentEvent regardless.
bun run dev fails the whole turbo run.lsof -nP -iTCP:2000 -sTCP:LISTEN.eve dev never fires schedules on their cron cadence, and everything visible
still works — the row is written, the sheet says Queued, and dispatch.ts is never
called. The poke covers this only when AGENT_BRIDGE_SECRET is set; unset,
poke() returns silently and the queue looks exactly like a slow agent.
Tasks the API did not write (schedule_recheck) and anything queued while the agent
was down still need a manual run:
bun run --filter=agent dispatch # exact production path, both lanes, real credits
Its printed sessionIds are research rows only, so a run that resolved forty logos
prints an empty list and was not idle. eve start and Vercel do run the schedule.
vercel env pull writes .env.local, which wins.env.local is the override the loader reads last, and vercel env pull writes
production credentials there by default. Pull once and every process silently
points at production — not as an error, but as bun run dev working perfectly against
the live database. On 2026-08-01 eleven migrations landed on Neon from a laptop.
vercel env pull .env.vercel.packages/db/scripts/require-local-db.ts guards db:migrate, db:push,
db:reset, db:seed and takes ALLOW_REMOTE_DB=1. db:deploy is unguarded on
purpose. It reads the root files directly rather than process.env, because Bun
auto-loads the working directory's .env while Prisma's CLI only sees
@crm/env/load.apps/api/scripts/build-func.mjs runs prisma migrate deploy during the crm-api
build, gated on VERCEL_ENV === "production". The schema therefore moves when the
release pull request merges and release deploys — with the code that needs it,
and once rather than once per branch.
Preview deploys share the production database: DATABASE_URL is a single value
across production, preview and development. Until that changes, a preview of a
branch that adds a migration runs against a database without those tables — it
builds, and the pages that touch them fail. Test schema changes locally, where
bun run dev migrates for you. Before the gate existed the reverse was true and
worse: every preview applied its own migrations to the production database, so on
2026-08-07 the live schema ran six migrations ahead of the live code all day.
migrate deploy is not proof the schema is rightThe build follows the deploy with prisma migrate diff --exit-code against
schema.prisma and shouts in the build log when they disagree. No pending migrations to apply only means _prisma_migrations has a row for every file —
it says nothing about what the tables actually look like.
They came apart once. A prisma db push shaped production from a laptop, the
migration rows were recorded as applied without their SQL ever running, and
agentConversationAttachment went live without its position column. Every deploy
reported nothing pending, for days, while conversations.builderById returned 500.
The tell is an object in the database that no migration defines — there was an
agentConversationAttachment_submissionId_createdAt_idx that appears in no
migration file, only in a db push of an older schema.
Reconciling is one command, and it is worth reading before running:
DATABASE_URL="…" bunx prisma migrate diff \
--from-config-datasource --to-schema prisma/schema.prisma --script
.gitignore ignores .env and .env.* with one negation for .env.example, so
.env.bak is ignored too. .env.example ships no secret — placeholders are empty
strings, asserted by packages/env/test/root.spec.ts. Generate your own secret;
never reuse one from an example, a tutorial, or another environment.
bun run --filter=api test
bun run --filter=agent test # integration specs need DATABASE_URL + real Postgres
bun run db:test creates crm_test and runs migrate deploy on it. The database
name must end in _test; the suite deletes rows it expects to put back, so it
refuses anything else.
migrate deploy only applies migrations that are missing. It never removes a
table, a column or a constraint the database has and the schema does not. A
crm_test built on a branch that was later abandoned therefore keeps that branch's
objects forever, and db:test used to report already exists and move on. The
extra objects are invisible until one of them rejects a write, and then the failure
names a constraint that appears in no migration and in no schema — a stray
trackedEvent_visitorId_fkey once failed seven tracking specs this way, on every
branch, for as long as the database survived.
So db:test now checks the database it found and rebuilds it when either is true:
schema.prisma, by prisma migrate diff. Something
was pushed or altered by hand.A rebuild drops the database and re-runs every migration, and it says which of the
two reasons fired. Force one with bun run db:test --reset. Nothing else in the
repo may drop a database, and this may only because the _test suffix is checked
first.
Then run bash setup.sh. It stops at the first error and is safe to run again. Servers and tests aren't started; it lists them at the end. Read it first.
git clone https://github.com/trycompai/crm.git cd crm
Node.js >=22, Bun, Docker
# needs nvm: https://github.com/nvm-sh/nvm nvm install --lts curl -fsSL https://bun.sh/install | bash # needs Docker Engine: https://docs.docker.com/engine/install/
Fill in secrets: BETTER_AUTH_SECRET
cp .env.example .env
Needs: PostgreSQL
# needs Docker Engine: https://docs.docker.com/engine/install/ docker compose -f docker-compose.yml up -d
bun install
bun run dev
bun run test
You need Bun and Docker.
git clone https://github.com/trycompai/crm.git && cd crm
cp .env.example .env # then fill in the values below
bun install
docker compose up -d # Postgres on :5432
bun run db:deploy # apply migrations
bun run db:seed # optional: a believable pipeline to look at
bun run dev
The app is on localhost:3000, the API on localhost:3001.
That clone gives you release, the default branch and the last tagged release — what you
want if you are running this. main is where unreleased work lands: green, but not cut
yet. If you are here to send a pull request, git switch main first and read
CONTRIBUTING.
Open .env and set these. Everything else in the file is optional and commented out.
| Variable | What to put in it |
| ------------------------------------------ | -------------------------------------------------------------------- |
| BETTER_AUTH_SECRET | openssl rand -base64 32 |
| ALLOWED_SIGN_IN | Your email domain, e.g. acme.com. Or one address, e.g. you@gmail.com. |
| GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET| A Google OAuth client — 2 minutes, below. Both or neither. |
| MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRET | A Microsoft Entra app registration — below. Both or neither. |
Pick at least one of Google and Microsoft, or add your own identity provider on Settings → SSO once you are in. Setting both is fine and common: the sign-in page offers both buttons, and each rep's mail is read from whichever they signed in with.
DATABASE_URL already matches the docker compose Postgres, so leave it alone unless
you brought your own.
http://localhost:3001/api/auth/callback/google..env.Google is the sign-in method a clone starts with, and the same client reads Gmail and Calendar — so most installs want it. The API will nonetheless boot without it: an install that signs in with Microsoft, or through its own identity provider added on Settings → SSO, leaves both empty and gets no Google button and no Gmail sync. Set them together or not at all; half a pair is a button that fails at Google. If your account is on a Google Workspace domain, set the consent screen to Internal and nobody outside your org can even reach the prompt.
</details> <details> <summary><strong>Getting the Microsoft app registration</strong></summary>ALLOWED_SIGN_IN still decides who gets an account — but the narrow choice turns
outsiders away at Microsoft rather than at our door.http://localhost:3001/api/auth/callback/microsoft. In production this is
https://<your-api-host>/api/auth/callback/microsoft — the API's origin, not the
app's, because the API is what serves /api/auth/*.offline_access is requested
at sign-in and needs no entry here. If your tenant requires admin consent, click
Grant admin consent so reps are not each asked..env as MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET.Mail.Read is read-only: the CRM can list and read messages and can never send,
reply, move or delete. Reading is forward-only — the first check records the current
time and imports nothing, so connecting an old mailbox does not dump years of mail into
the CRM.
To refuse other tenants at Microsoft rather than relying on `ALLOWED_SIGN
…
No reports for Linux yet.
GitManual AI Agent reads the 10 key files and writes a step-by-step guide for Linux. Once written, it's saved for everyone.