Chloei Code is a production-ready MVP control plane for Cursor cloud coding agent runs against GitHub repositories.
The app lets an authenticated user create, monitor, refresh, cancel, retry, and review Cursor cloud agent runs. It stores raw Cursor statuses/events defensively, keeps CURSOR_API_KEY server-side only, and supports optional Cursor pull request creation.
- Next.js App Router, TypeScript, React 19
- Cursor TypeScript SDK:
@cursor/sdk - Postgres with Prisma
- Auth.js / NextAuth v5 beta with GitHub OAuth and Prisma adapter
- Tailwind CSS v4 and shadcn/ui
- Vercel Web Analytics and Speed Insights
- Vitest for unit tests
pnpm install
cp .env.example .env
pnpm db:generate
pnpm db:migrate
pnpm devThe Cursor SDK currently pulls in sqlite3; this repo allows that native build through pnpm-workspace.yaml.
Open http://localhost:3000.
Required:
DATABASE_URL: Postgres connection string.CURSOR_API_KEY: Cursor Cloud Agents API key from Cursor Dashboard integrations.AUTH_SECRET: Auth.js secret.AUTH_GITHUB_ID: GitHub OAuth app client ID.AUTH_GITHUB_SECRET: GitHub OAuth app secret.
The GitHub OAuth provider requests read:user, user:email, repo, and
read:org so the app can list the signed-in user's own, collaborator, private,
and organization repositories without exposing the GitHub token to the browser.
Existing local sessions created before these scopes were added should sign out
and sign back in to grant the expanded repository discovery scope.
Optional:
ALLOW_DEV_AUTH_BYPASS=true: local development only. Creates/uses adev-userwithout OAuth.DEFAULT_CURSOR_MODEL: optional model ID. It is validated againstCursor.models.list()before run creation.ALLOWED_GIT_HOSTS: comma-separated allowed Git hosts. Defaults togithub.com.ALLOWED_GITHUB_ORGS: comma-separated GitHub owners/orgs allowed for submitted repository URLs.ALLOWED_GITHUB_USERS: comma-separated GitHub usernames allowed to sign in. If empty andALLOWED_EMAILSis also empty, any GitHub OAuth user can sign in.ALLOWED_EMAILS: comma-separated email addresses allowed to sign in. If either user or email allowlist is configured, a sign-in must match one of them.AGENT_RUN_RATE_LIMIT: create/cancel/retry requests per user per minute. Defaults to10.AGENT_RUN_ACTIVE_LIMIT: maximum activecreating/runningCursor runs per user. Defaults to3; set0to disable.AGENT_RUN_DAILY_LIMIT: maximum runs created per user in a rolling 24-hour window. Defaults to25; set0to disable.CRON_REFRESH_BATCH_SIZE: maximum active runs refreshed by each scheduled cron invocation. Defaults to10.STALE_ACTIVE_RUN_MINUTES: minutes before an active run is reported as stale on/status. Defaults to15.CRON_SECRET: bearer token for the Vercel cron endpoint that refreshes active Cursor runs.
Never expose Cursor credentials through NEXT_PUBLIC_* variables.
For production, configure a dedicated GitHub OAuth app with:
Homepage URL: https://your-production-domain
Authorization callback URL: https://your-production-domain/api/auth/callback/github
For local development, use a separate GitHub OAuth app with:
Homepage URL: http://localhost:3000
Authorization callback URL: http://localhost:3000/api/auth/callback/github
This project is currently configured with separate GitHub OAuth apps for:
- Local development:
http://localhost:3000 - Preview branch deployments:
https://chloeicode-git-preview-chloei.vercel.app - Production:
https://chloeicode.vercel.app
Vercel preview OAuth is intentionally tied to the preview branch URL because
GitHub OAuth callback URLs must be stable. Ad hoc preview URLs can still deploy,
but sign-in should be tested on the preview branch deployment.
The server-side wrapper lives under src/lib/cursor.
Cursor.models.list()populates the model selector.Cursor.repositories.list()provides connected repository suggestions.Agent.create({ cloud })creates repo-scoped cloud agents.agent.send(prompt, { idempotencyKey })starts a run.Agent.getRun(runId, { runtime: "cloud", agentId })reconnects.run.stream(),run.wait(), andrun.cancel()power live updates and actions.
The installed SDK types are the implementation source of truth. Cursor cloud API shapes are beta, so raw statuses, events, and result payloads are stored as JSON alongside normalized app status.
The new-run form loads repositories from:
GET /api/github/repositories
When a repository is selected, the form also loads branches from:
GET /api/github/branches?repoUrl=https://github.com/owner/repo
That route reads the signed-in user's GitHub OAuth token from the server-side
Auth.js Account record and calls GitHub's authenticated repository API. The
client receives only repository and branch metadata, never the OAuth token.
Cursor-connected repositories from Cursor.repositories.list() are merged into
the same selector and tagged when a GitHub repository is also connected in
Cursor.
Completed runs with a Cursor-created PR show live GitHub metadata on the run detail page. The server loads PR state, checks, commit statuses, reviews, inline review comments, branch existence, and safe cleanup eligibility through:
GET /api/agent-runs/:id/pull-request
POST /api/agent-runs/:id/pull-request/cleanup
Cleanup closes the linked PR and deletes only the matching stored head branch when it is in the same repository, is not the base/default branch, and still matches the branch returned by Cursor for that run. Cleanup writes an app event to the run log.
The Prisma schema includes:
- Auth.js
User,Account,Session, andVerificationTokenmodels. AgentRunfor Cursor IDs, repo/ref/model/prompt, normalized/raw statuses, PR metadata, result payloads, and retry linkage.AgentRunEventfor ordered raw Cursor/app events.AgentRunArtifactfor optional SDK artifact metadata.
Run pnpm db:migrate after configuring a real DATABASE_URL.
The Vercel project is configured to use separate Neon branches for runtime database isolation:
- Production (
main): Neon branchmain - Preview branch deployment (
preview): Neon branchpreview - Development/local: Neon branch
development
The app reads DATABASE_URL at runtime. Additional Neon/Postgres URL and host
variables are kept aligned in Vercel for operator tooling, but application code
should continue to use DATABASE_URL only. Non-production branches are intended
for testing and should not be treated as production data stores.
pg_stat_statements is enabled on production for database egress diagnostics.
Current observed top-row queries are dominated by Neon internal monitoring, not
application overfetching. Re-run the stats after meaningful production traffic
before making query tuning changes.
Run detail pages load persisted events first, then attach to:
GET /api/agent-runs/:id/stream
The SSE route persists Cursor stream events and finalizes with run.wait() when the stream closes. The UI also polls persisted events as a fallback. On Vercel, vercel.json schedules:
GET /api/cron/refresh-runs
every five minutes. The endpoint requires Authorization: Bearer $CRON_SECRET, refreshes a bounded batch of active runs, and records a safe app event if a background refresh fails. Final status is not solely dependent on a connected browser tab. A durable queue/worker is still recommended if run volume grows.
Run creation is guarded before any Cursor SDK call by:
AGENT_RUN_RATE_LIMIT: in-memory per-process request rate limit.AGENT_RUN_ACTIVE_LIMIT: persistent per-user active run cap.AGENT_RUN_DAILY_LIMIT: persistent per-user rolling 24-hour run cap.
The new-run page displays current capacity, and /api/agent-runs/limits
returns the same server-calculated policy for operator tooling.
Signed-in users can open:
/status
to verify database, Cursor API, GitHub API, Vercel runtime, and active-run health. A basic unauthenticated health endpoint is also available at:
GET /api/health
The basic endpoint reports only safe configuration status and does not expose secret values.
Vercel Web Analytics and Speed Insights are installed in the root App Router layout, so production deployments report page views and real-user performance metrics to the Vercel project dashboard.
- All Cursor SDK calls happen in route handlers or server-only modules.
- Repository URLs are validated, normalized, and restricted by host/org env vars.
- Per-user ownership checks guard every run, event, action, and artifact endpoint.
- Production sign-in can be restricted with
ALLOWED_GITHUB_USERSand/orALLOWED_EMAILS. - Create/cancel/retry routes use an in-memory per-process rate limiter for burst control, plus persistent per-user active and 24-hour run caps for costly Cursor run creation. Use a shared limiter for stricter multi-instance burst control.
- Raw event JSON is escaped by React and shown only in explicit debug disclosures.
- The app never accepts arbitrary shell commands as a separate input.
pnpm dev
pnpm build
pnpm lint
pnpm typecheck
pnpm test
pnpm db:generate
pnpm db:migrate
pnpm db:studioGitHub Actions runs pnpm lint, pnpm typecheck, pnpm test, and
pnpm build on pushes to main/preview and pull requests into main.
Protect main by requiring the quality status check before merging.
- Configure Cursor/GitHub integration and env vars.
- Create a run against a small test repository.
- Confirm it appears on the dashboard with normalized and raw status.
- Confirm detail page events update through SSE or polling.
- Cancel an active run and verify status/event persistence.
- Retry a completed or failed run and verify the retry links to the original.
- Confirm PR URL and branch metadata appear when Cursor returns them.
- Confirm the PR panel shows GitHub checks/review comments and can clean up a disposable test PR.
- Inspect browser HTML, network payloads, and bundles for absence of
CURSOR_API_KEY.
- No models: verify the API key belongs to a Cursor account/team with cloud agents enabled.
- Run creation fails with
usage_limit_exceeded: enable Cursor usage-based pricing and set a spend limit with at least $2 remaining before starting a Background Agent run. - Repo unavailable: confirm Cursor’s GitHub integration has access, or enter a valid allowed GitHub URL manually.
- GitHub repos missing: sign out and sign back in so GitHub grants the current repository discovery scopes. Also confirm organization SSO/access policies.
- No PR URL: PR creation depends on Cursor settings, repo permissions, and the
autoCreatePRtoggle. - Stream disconnects: refresh the page; persisted events are the source of truth.
- Artifact list empty: artifact support depends on installed SDK/runtime behavior for the run.