Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗AI employee desk — Mission Control for agents you create. Default display name is always New Agent. Role is a label. You rename freely. Jobs plan → use tools → produce artifacts. You approve what leaves.
The GitHub repo remains mrosmanyt/brandcrew; the product is CINEM Pro. CINEM (cinem.tech) is the company.
This is a vertical slice, not a Strawberry clone: no per-agent VMs, no LinkedIn auto-post, no live email/WhatsApp send. Installing a Marketplace bot or launching a team only creates Agent rows — it does not invent business results. Jobs can browse public pages (browser_navigate / browser_snapshot / crawl_links) and, with the Chrome side panel paired, click / type / extract / screenshot on a live tab. Desktop Playwright is optional. Without the extension, interact tools return a clear “needs the Chrome extension” result instead of fake success.
The public site is Replit-simple (warm paper, generous space, one primary CTA). Mission Control is a Grok Bot–style agent desk (sidebar agents, chat-first, jobs you approve). Visual tokens live in src/app/globals.css.
/login when signed out and to desk settings when signed in. Onboarding creates a free workspace with the Northline Studio Brand Kit (sample company facts, not fake job output). The first-run flow is a one-step-at-a-time wizard (agent → website → integrations → Brand Kit → model), with Skip to Mission Control./desk/[workspaceId]). A 3-step first-run card (New Agent → first job → Approve) can be dismissed; completion is stored per workspace member./desk/[workspaceId]/marketplace): Plugins, Bots, Companions, and Playbooks (agency set: prospecting, outreach pack, weekly/daily brief, SEO brief, multi-tab research, client-named email, follow-up, competitor watch, talent sourcing — plus LinkedIn week / competitor scan / website). Composio connectors (Gmail, HubSpot, Apollo, Ahrefs, …) need COMPOSIO_API_KEY. Missing key stays disconnected. Run first tool call proves the SDK (Gmail profile if Connected, else a Hacker News read).Agent (name still “New Agent”, role/instructions from the template). Added if that template id is already installed. Add companion (Prospect Peter, Recruiter Ryan, Invoice Ivy, Content Casey, Research Riley) → real Agent with that name, instructions, and allowed tools. Custom companion: name + instructions + tool groups.PluginConnection. Connected only with a real API key (or documented server env) or a successful OAuth callback. Empty Connect / missing OAuth client ids stay disconnected.read_brand_kit, browser_navigate / browser_snapshot / browser_click / browser_type / browser_extract / crawl_links / web_search / write_artifact, then ask_user. Clarify pauses show Yes/No on the desk and persist Job.askKind + Job.userAnswer in Postgres. Browse events show the tool name + URL./console in a new tab. Mint a workspace key, call /api/v1 from the try panel or curl. Brand Kit is under Settings, not the main sidebar.Quick-start chips follow the selected agent’s role label (Research → Competitor scan, Sales → Outreach from research). If you have no Research agent, the chip says Add Research bot from Marketplace — chips never invent a named roster.
Natural language such as “poori team banao” or “create agents for my whole business” opens the same approve sheet — it does not silently spawn a roster.
Playbooks run on the user agent you selected (agentId). Role is only a hint:
| Chip / job | Role hint | What happens |
|---|---|---|
| LinkedIn week | Content / Writer | Brand Kit → (optional browse if you paste a URL) → five LinkedIn posts → needs_you |
| Research pack | Research | Brand Kit → browser_navigate + optional crawl_links → sourced notes |
| Competitor scan | Research | Brand Kit → browse URLs from your message or the Brand Kit website → comparison artifact |
| Outreach from research | Sales | read_artifact (latest research/competitor pack) → 5 LinkedIn DMs → approval |
| Ad angles from URL | Ads | Browse a landing page → 5 angles. No media buy |
| Build website | Website | Brand Kit → HTML landing page artifact → in-desk iframe preview. Not published |
| Build app | App | Brand Kit → HTML mini-app artifact → iframe preview. No Replit login |
| Prospecting scan | Sales | Brand Kit → navigate + snapshot + extract → sourced notes + Uncertainty → needs_you. Does not invent contacts or send |
| Outreach draft pack | Sales | Latest research (or a pasted URL) → 5 drafts → approval. Does not send |
| Weekly client brief | Research | Navigate + snapshot + short crawl → sourced brief. Does not invent results or email the client |
The cloud keeps accounts, billing, schedule, and audit. Browser tools prefer your Chrome (MV3 + chrome.debugger CDP). CINEM Pro is supervised — not a fully autonomous employee. High-risk writes (send email, Slack post, payments, local file write) always pause. Gmail drafts, list mail, research, and in-desk artifacts do not. Always approved on the composer auto-runs safe click/type; it never skips sends, posts, or payments.
Install path
public/downloads/cinem-pro-chrome.zip, also /api/downloads/extension).chrome://extensions → Developer mode → Load unpacked → folder with manifest.json (until the Chrome Web Store listing is live — docs/chrome-extension-store.md).node native-host/install.mjs --extension-id=<id from chrome://extensions>
node native-host/host.mjs --http # 127.0.0.1:43181 — also spawned by Electron
Desktop Windows installer and Android Play path are on /download. Cinem AI Assistant (Windows-only native app, included with your CINEM Pro plan) is on /cinem-ai-assistant — source in apps/cinem-ai-assistant/, contract docs/cinem-ai-assistant.md. Windows installer: Actions → Cinem AI Assistant Windows (Cinem-AI-Assistant-Setup.exe). Auth across web / desktop / extension / Android: docs/auth-bridge.md. Play Store: docs/play-store-launch.md. Windows SmartScreen / code signing: docs/windows-code-signing.md.
Security baselines: page text is wrapped in <<<CINEM_UNTRUSTED_PAGE_CONTENT>>> (data, never instructions); writes go through the approval queue; each job has a domain allowlist and aborts if the agent leaves allowed hosts. Audit lines live on the On-device page and in WorkspaceAudit.
Credits in the desk header wrap Free / Pro / Pro Plus / Ultra token budgets 1:1. Billing is unchanged.
Phase 2 cost controls (routines, action cache, triggers, replay, Always-approved) are shipped. Phase 3 adds Composio sessions, learning memory, multi-tab research, and client workspaces — see AGENTS.md.
After this branch deploys, these must HTTP 200 (not /404):
curl -sI https://app.cinem.tech/brand/cinem-logo.png
curl -sI https://app.cinem.tech/brand/cinem-mark.svg
curl -sI https://app.cinem.tech/og.png
curl -sI https://brandcrew.vercel.app/brand/cinem-logo.png
curl -sI https://brandcrew.vercel.app/brand/cinem-mark.svg
curl -sI https://brandcrew.vercel.app/og.png
Expect 200 and content-type: image/png (or image/svg+xml for the mark). Login / nav / signup render <img src="/brand/cinem-logo.png"> via BrandMark, not a CP badge. Files live at public/brand/cinem-logo.png, public/brand/cinem-mark.svg, public/og.png. Production was previously stuck on an old deploy because next build typechecked scripts/check-launch.ts (process.env.NODE_ENV is readonly) — that assignment now goes through a mutable env bag.
| Server keys | What jobs persist |
|---|---|
Any of OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, XAI_API_KEY | Model text, or tool-captured browse/search text. Never canned Northline “tasting menu” copy. |
| None | Labeled offline templates from the Brand Kit. Banner says so. |
PLAYWRIGHT_ENABLED turns on headless Chrome when a binary is present. If Playwright is off or Chrome is missing, browse tools fall back to fetch and still crawl a couple of public links. They never invent page text.
| Tab | Action | Persistence |
|---|---|---|
| Bots | Add / Added | Agent with templateId. Capability only — no fake artifacts. |
| Plugins | Connect / Connected | PluginConnection (pluginId, status, non-secret metadata). Secrets encrypted with SESSION_SECRET, never returned to the client, never committed. |
v1 Connect that actually works:
TAVILY_API_KEY when that env is set. Empty form → still disconnected./api/oauth/callback. If client ids are missing, the UI says so and does not fake Connected.Connected Web Search exposes web_search. Connected Gmail exposes gmail_list_recent and gmail_create_draft (never send). Connected Slack exposes slack_list_channels, slack_draft_message, and slack_post_message (post only after ask_user). Missing OAuth client ids → Connect stays disconnected. Notion / Calendar / Drive still store tokens after callback; they do not auto-post.
Continue with Google on /login and /signup creates or links a User and sets the same brandcrew_session cookie as email/password. This is not Marketplace Gmail connect.
| Purpose | Scopes | Callback |
|---|---|---|
| User login | openid email profile | /api/auth/google/callback |
| Gmail plugin | gmail.readonly + gmail.compose | /api/oauth/callback |
Reuse GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET. On the same Google Cloud Web application client, add all of these Authorized redirect URIs (exact match, including 127.0.0.1 vs localhost):
http://127.0.0.1:43180/api/auth/google/callbackhttps://app.cinem.tech/api/auth/google/callbackhttps://brandcrew.vercel.app/api/auth/google/callbackhttp://127.0.0.1:43180/api/oauth/callbackhttps://app.cinem.tech/api/oauth/callbackhttps://brandcrew.vercel.app/api/oauth/callbackAuthorized JavaScript origins:
http://127.0.0.1:43180https://app.cinem.techhttps://brandcrew.vercel.appSet OAUTH_REDIRECT_BASE (or APP_URL / NEXT_PUBLIC_APP_URL) to the origin you are serving. Missing client id/secret shows a setup tip on the login/signup button — CINEM Pro does not fake a signed-in session or a Connected plugin.
Optional dedicated login client: GOOGLE_LOGIN_CLIENT_ID / GOOGLE_LOGIN_CLIENT_SECRET. Consent screen must include the OpenID scopes (openid, email, profile) in addition to any Gmail plugin scopes.
https://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/gmail.compose127.0.0.1 vs localhost):
{OAUTH_REDIRECT_BASE or APP_URL or NEXT_PUBLIC_APP_URL}/api/oauth/callbackhttp://127.0.0.1:43180/api/oauth/callback.env as GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (or GMAIL_CLIENT_*).OAUTH_REDIRECT_BASE (or APP_URL / NEXT_PUBLIC_APP_URL) to that same origin.npm run dev. Marketplace → Plugins → Connect on Gmail → Google consent → redirect back. Connected only after token exchange. Reconnect repeats consent. Disconnect clears encrypted tokens.access_denied): while the OAuth consent screen is in Testing, Google only allows listed Test users. Add each Gmail (for example the founder’s account) under Google Cloud → APIs & Services → OAuth consent screen → Test users, or publish the app to Production. CINEM Pro shows this on Connect failure and does not mark Gmail Connected. User-cancelled consent is also access_denied; the desk still stays disconnected.Job tools when Connected: gmail_list_recent (subject / from / date), gmail_create_draft (creates a Gmail draft — does not send, no approval prompt). Sending mail is always gated if it is ever added. Access tokens refresh via the stored refresh_token; Google only returns refresh_token on the first consent (prompt=consent + access_type=offline).
{OAUTH_REDIRECT_BASE or APP_URL or NEXT_PUBLIC_APP_URL}/api/oauth/callbackchannels:read — list public channels (conversations.list)groups:read — list private channels the bot can seechat:write — chat.postMessage after approvalSLACK_CLIENT_ID / SLACK_CLIENT_SECRET.oauth.v2.access succeeds on the callback.refresh_token / expires_in) is stored when Slack returns it; long-lived bot tokens work without expiry.Job tools when Connected: slack_list_channels, slack_draft_message (artifact, not posted), slack_post_message only if a prior ask_user step is done. Approving the draft resumes the job and then posts.
Manual click-through: with env credentials set, Connect → provider consent → return to Marketplace with ?connected=gmail or ?connected=slack. Without credentials, Connect stays honest (error, not Connected).
Mission Control has one agents list (the left sidebar). Avatars are 3D geometric shapes derived from agent id/name; they animate while a job is queued/running.
Next.js (App Router) · TypeScript · Tailwind · Postgres via Prisma (Neon or Docker) · session cookies · OpenAI + Anthropic + Gemini + optional xAI · Whop checkout (Stripe fallback) · optional Electron desktop
Workspace-scoped REST at /api/v1. Mint keys in API Console (/console). Secrets are shown once; only SHA-256 hashes are stored. Keys never include model provider secrets.
Auth: Authorization: Bearer cinem_live_… (session cookies are ignored). Errors are JSON { "error": "…", "code": "unauthorized" }. Rate limit: 60 requests / minute / key.
| Method | Path | Notes |
|---|---|---|
GET | /api/v1 | Workspace + endpoint catalog |
GET | /api/v1/workspace | Key’s workspace |
GET | /api/v1/agents | List agents |
POST | /api/v1/agents | Create agent (name still defaults to New Agent) |
GET | /api/v1/agents/:agentId | One agent |
GET | /api/v1/jobs | Recent jobs (?agentId=) |
POST | /api/v1/jobs | Queue a job { agentId, message } — same runtime as Mission Control |
GET | /api/v1/jobs/:jobId | Status, events, artifacts |
GET | /api/v1/artifacts | List (?jobId= / ?agentId=) |
GET | /api/v1/artifacts/:artifactId | One artifact |
Jobs do not auto-publish. Slack chat.postMessage still requires a prior ask_user in the desk (Always approved does not skip it). Gmail creates drafts without a prompt and never sends.
curl -sS http://127.0.0.1:43180/api/v1 \
-H "Authorization: Bearer cinem_live_YOUR_KEY"
curl -sS -X POST http://127.0.0.1:43180/api/v1/agents \
-H "Authorization: Bearer cinem_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"role":"Research"}'
curl -sS -X POST http://127.0.0.1:43180/api/v1/jobs \
-H "Authorization: Bearer cinem_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"agentId":"AGENT_ID","message":"Competitor scan of the Brand Kit site."}'
curl -sS http://127.0.0.1:43180/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer cinem_live_YOUR_KEY"
Key create/list/revoke (session cookie, desk UI):
GET / POST /api/workspaces/:workspaceId/api-keysDELETE /api/workspaces/:workspaceId/api-keys/:keyIdThese routes live on the existing catch-all /api/[...path] handler (Hobby function budget).
Postgres is required (Prisma provider is postgresql). Local Docker is the default; a Neon free database also works. SQLite file:./dev.db is not supported anymore — existing files are not migrated.
docker compose up -d
npm install
cp .env.example .env
npx prisma migrate deploy
npm run dev
npm run dev copies .env.example → .env when missing, rewrites leftover SQLite URLs, and runs prisma migrate deploy. If Postgres is down it tries docker compose up -d first.
The desk listens on http://127.0.0.1:43180.
CINEM Pro can run in an Electron window like a local Grok Bot — not only npm run dev in a browser.
npm install
npm run desktop:cloud # production desk (https://app.cinem.tech) — no Postgres
npm run desktop:dev # local Next on http://127.0.0.1:43180
Packaged Setup.exe users always get the cloud desk. desktop:dev is for contributors who already have Docker/Neon. Mission Control, agents, Marketplace, Gmail/Slack OAuth, and job tools are the same app as the website.
Needs git + Node 20+:
curl -fsSL https://raw.githubusercontent.com/mrosmanyt/brandcrew/main/scripts/install-desktop.sh | bash
Or: bash scripts/install-desktop.sh from a clone. Override checkout with BRANDCREW_DIR, BRANDCREW_REF, BRANDCREW_REPO.
Downloads CINEM-Pro-Setup.exe (cloud desk + Cinem AI Assistant). SmartScreen: More info → Run anyway.
irm https://raw.githubusercontent.com/mrosmanyt/brandcrew/main/scripts/install-desktop.ps1 | iex
Or: powershell -File scripts/install-desktop.ps1. Dev checkout: $env:CINEM_INSTALL_DEV=1.
Hosted Windows builds (public repo cinem-pro-releases, GitHub Release latest):
latest.yml + *.blockmap on the same release (desktop-auto-update.md)The marketing Download buttons use those exact asset URLs — not README anchors. Installers are public, so anonymous visitors get a real file without GitHub login. There is no hosted Mac .dmg (Linux cannot produce a usable one).
npm run desktop:build:win # NSIS CINEM-Pro-Setup.exe + portable .exe (x64)
npm run desktop:build:mac # .dmg + .zip — run on macOS
npm run desktop:build # current platform (Linux → AppImage)
Artifacts land in dist/desktop/. Publish:
gh release create cinem-pro-v0.3.4 \
dist/desktop/CINEM-Pro-Setup.exe \
dist/desktop/CINEM-Pro-Portable.exe \
dist/desktop/latest.yml \
--repo mrosmanyt/cinem-pro-releases \
--title "CINEM Pro 0.3.4" \
--notes "Windows installer — cloud desk + Cinem AI Assistant. Desk Google sign-in uses Sign in with CINEM Pro."
Where keys live
| Mode | .env | Postgres |
|---|---|---|
| Packaged Setup.exe (default cloud) | none — loads https://app.cinem.tech | not used |
npm run desktop:cloud | none | not used |
desktop:dev / npm run dev | project .env | DATABASE_URL (Docker on :5432 or Neon) |
Packaged CINEM_DESK_MODE=local | macOS ~/Library/Application Support/CINEM Pro/.env · Windows %APPDATA%\CINEM Pro\.env | same DATABASE_URL / DIRECT_URL (Docker or Neon). First launch writes the local Docker URL. Apply schema with npx prisma migrate deploy against that URL. |
Cloud-mode Google login uses the production redirect https://app.cinem.tech/api/auth/google/callback. Local-mode OAuth still uses http://127.0.0.1:43180/api/oauth/callback (OAUTH_REDIRECT_BASE, override port with BRANDCREW_PORT).
Cross-build limits (honest):
.dmg: run desktop:build:mac on macOS. Linux cannot produce a usable signed/stapled dmg (electron-builder will skip or fail; that is expected)..exe: desktop:build:win on Windows is the straightforward path. On Linux, wine32 (i386) is required for a complete NSIS CINEM-Pro-Setup.exe — wine64 alone leaves a tiny stub. Portable .exe still builds without wine32. package.json keeps signAndEditExecutable: false so unsigned local builds always work. desktop:build:win turns signing on only when Azure Artifact Signing or CSC_LINK credentials are complete — see docs/windows-code-signing.md. Unsigned Setup.exe shows SmartScreen until a signed build is published to cinem-pro-releases..github/workflows/desktop-windows.yml) builds the unified NSIS on windows-latest. Vercel / Next CI does not. Do not expect a Mac dmg from a Linux agent./signup and create an email/password account.web_search in jobs.See .env.example. Summary:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | yes | Postgres connection string. Local Docker default is in .env.example. Neon: pooled URL (sslmode=require, add pgbouncer=true if using the pooler). |
DIRECT_URL | yes | Unpooled Postgres URL for prisma migrate deploy. Local Docker: same as DATABASE_URL. Neon: the direct connection string. |
SESSION_SECRET | yes (dev default provided) | Signs the session cookie and encrypts plugin secrets. Change in production. |
ADMIN_EMAILS | no ([email protected] and [email protected] always included) | Comma-separated staff emails for Internal Admin HQ at /admin. Set on Vercel for every operator or they get 403. See docs/admin-ops.md. |
OPENAI_API_KEY | no | OpenAI. Cheap drafts (gpt-4o-mini) and GPT-4.1-class finals when Claude is unset. |
ANTHROPIC_API_KEY | no | Claude. Preferred for strong finals (claude-sonnet-5). |
GEMINI_API_KEY | no | Gemini. Preferred cheap drafts (gemini-2.5-flash). Sole provider uses Flash + Pro. |
GOOGLE_GENERATIVE_AI_API_KEY | no | Alias for GEMINI_API_KEY. |
TAVILY_API_KEY | no | Web Search plugin. Jobs call Tavily only when the plugin is Connected. |
PLAYWRIGHT_ENABLED | no | Headless browse. Local: defaults on when Chrome is found. Vercel: defaults off. Set false in Production. |
PLAYWRIGHT_CHROME_PATH | no | Override Chrome/Chromium binary for Playwright. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | no | Google OAuth (Gmail, Calendar, Drive). Redirect: {OAUTH_REDIRECT_BASE or APP_URL or NEXT_PUBLIC_APP_URL}/api/oauth/callback. |
API keys are read only on the server. Users never paste LLM keys. Plugin keys are workspace-scoped and encrypted.
This repo is deploy-prep only — it does not create cloud accounts or push a production deploy from CI.
DATABASE_URL (Neon “pooled”; add ?sslmode=require. If you use the pooler host, also add &pgbouncer=true).DIRECT_URL (required for prisma migrate deploy).export DATABASE_URL='postgresql://...'
export DIRECT_URL='postgresql://...' # unpooled
npx prisma migrate deploy
The initial migration is prisma/migrations/20240907120000_init.
mrosmanyt/brandcrew at vercel.com/new.vercel.json runs prisma generate && prisma migrate deploy && next build.| Env | Production value |
|---|---|
DATABASE_URL | Neon pooled URL |
DIRECT_URL | Neon direct URL |
SESSION_SECRET | long random string (not the example) |
NEXT_PUBLIC_APP_URL | https://app.cinem.tech (Preview: this deployment’s URL or leave unset so siteOrigin() uses VERCEL_URL) |
APP_URL | same origin |
OAUTH_REDIRECT_BASE | same origin |
PLAYWRIGHT_ENABLED | false |
BILLING_MOCK | false for live Whop (leave true only for demo) |
WHOP_API_KEY | Whop Account API key |
WHOP_COMPANY_ID | biz_… from the Whop dashboard |
WHOP_WEBHOOK_SECRET | Webhook signing secret |
WHOP_STARTER_PLAN_ID / WHOP_PRO_PLAN_ID / WHOP_ULTRA_PLAN_ID | optional existing plan ids |
WHOP_SUPPORT_PRODUCT_ID | optional Support product (prod_…) for one-time tips |
OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY | optional; no keys → offline templates |
ADMIN_EMAILS | comma-separated staff emails that may open /admin. [email protected] and [email protected] are always included. Set this on Vercel for every operator (QA included) or they get 403. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | optional; Gmail Connect |
SLACK_CLIENT_ID / SLACK_CLIENT_SECRET | optional |
npm run build locally does not run migrate deploy (so it works without a live DB). Vercel’s vercel-build / vercel.json does.
checkout_configuration:create, plan:create, plan:basic:read, and webhook receive/read as needed. Store the key as WHOP_API_KEY.biz_…) into WHOP_COMPANY_ID (CINEM Tech production: biz_VrtL8S4duREQg4). Live checkout sends this as company_id and plan.company_id, plus account_id as an alias. Empty company id is a ClientError — CINEM Pro does not call Whop without it.WHOP_STARTER_PLAN_ID, WHOP_PRO_PLAN_ID, WHOP_ULTRA_PLAN_ID. If those are empty, checkout creates a matching monthly renewal inline.prod_… to WHOP_SUPPORT_PRODUCT_ID. CINEM Pro does not use a fixed Support price — checkout creates a hidden one-time plan with initial_price equal to the amount the buyer typed ($1–$99,999). Whop has no pay-what-you-want field on checkout configuration. Full steps: docs/whop-support.md.https://app.cinem.tech/api/webhooks/whop (alternate https://brandcrew.vercel.app/api/webhooks/whop)v1payment.succeeded, membership.activated, membership.deactivatedws_…) into WHOP_WEBHOOK_SECRET on Vercel. Never commit it.BILLING_MOCK=false (or unset it) so desk Plans redirects to Whop instead of applying a fake upgrade.Cancel behavior: membership.deactivated returns the workspace to Free when that membership is the one that granted the current paid plan (matched by whopMembershipId or metadata.plan). Upgrading Pro → Ultra then cancelling the old Pro membership does not drop Ultra.
Use the same origin as OAUTH_REDIRECT_BASE:
https://<project>.vercel.app/api/oauth/callback
Add that exact URI (plus https://your-domain/api/oauth/callback if you attach a domain):
Local desktop stays http://127.0.0.1:43180/api/oauth/callback. Keep both URIs on the OAuth clients if you use desktop and Vercel.
https://<project>.vercel.app loads/signup creates an account (writes to Neon)/api/oauth/callback on the Vercel origindesktop:cloud opens https://app.cinem.tech/desk (no local Postgres)desktop:dev still works against local Docker/Neon DATABASE_URLPublic site: app.cinem.tech (Vercel alias brandcrew.vercel.app). Product name CINEM Pro, company CINEM.
HTTPS: Vercel terminates TLS and redirects HTTP→HTTPS on *.vercel.app. This app also sends Strict-Transport-Security: max-age=31536000 (no preload on a vercel.app subdomain) and CSP upgrade-insecure-requests. Headers live in one module: src/lib/security-headers.ts (applied from next.config.ts and src/proxy.ts). Follow-up hardening (nonce CSP, COOP/COEP) should extend that file — do not add a WAF product.
CSRF: Session cookie brandcrew_session is httpOnly, SameSite=Lax, Secure in production and on Vercel. Same-origin POSTs send it; cross-site POSTs from other origins do not. OAuth callbacks are top-level GET. There is no extra CSRF token. The session JWT is never written to localStorage / sessionStorage (those stores are cookie-banner consent, desk pane width, billing toast, and developer API keys — not login).
Auth (existing Google + cookie session — no second system): Continue with Google already verifies email (email_verified === true or the callback bounces email_unverified). Email/password signup remains. When Supabase requires confirmation, POST /api/auth/signup returns needsEmailConfirmation, the screen asks the person to check their email, and the link returns through /auth/callback into /onboarding. There is no password-reset route and no 2FA UI. Privileged Admin HQ is ADMIN_EMAILS + requireAdmin / loadAdminPage on every /admin page and /api/admin GET+POST (including /api/admin/backup) — hiding the Settings link is not the gate. Password signup and Settings password-change require 8–72 characters, reject trivial passwords, and optionally query Have I Been Pwned (k-anonymity SHA-1 prefix, 2s timeout, fail-open). 2FA is a follow-up.
Rate limits: In-memory per-IP windows on login, signup, Google start/callback, checkout, admin reads, admin writes (tighter), account PATCH, and invite accept. Login/signup also bucket per email (cloned request body; Hobby has no Redis). Isolates do not share memory (Upstash-free). Developer API keys already have a 60/min hashed-key window in Postgres.
| Item | Status |
|---|---|
| Logo in nav, desk, admin, favicon, apple-touch, OG | Done (official PNG public/brand/cinem-logo.png + SVG public/brand/cinem-mark.svg) |
Privacy (/privacy) + Terms (/terms) | Done |
| Footer Privacy / Terms text links (no new top-nav menus) | Done (extended existing footer grid) |
| Secrets off the frontend | Already server-only LLM/plugin keys; this pass sanitizes 500s in production |
| Force HTTPS | Already Vercel HTTP→HTTPS; Done HSTS + upgrade-insecure-requests + README |
| Cookie consent banner | Done (non-blocking; analytics only after Accept) |
| Meta titles + descriptions | Done (metadataBase, title template, page titles) |
Social preview (og:image / Twitter) | Done (src/app/opengraph-image.png, twitter-image.png, public/og.png) |
| Favicon + apple touch icon | Done |
| Sitemap + robots.txt | Done (/sitemap.xml, /robots.txt; desk/admin/api disallowed) |
| Alt text on key marketing images | Already hero demo aria-label; live screenshot alt; connector marks decorative next to labels |
| Compress / avoid huge assets | Done (SVG mark; geometric PNGs, no new photo dumps) |
| Color contrast on new pages | Done (legal pages use text-foreground on the marketing canvas) |
| Mobile-friendly new pages | Done |
| Custom 404 | Already present; logo + Get started CTA wired |
| Main nav anchors unchanged | Already; Privacy/Terms are footer-only |
| Auth form validation | Already zod + required fields; extra client checks |
npm run test:launch covers headers, rate limit, honeypot, password rules, session-cookie audit, admin gates, sanitized errors, sitemap/robots, and “no CP placeholder”.
fetch + a short public crawl. Not Browserbase.rhel-openssl-3.0.x binary on Vercel. Local/desktop generate native as well.https://app.cinem.tech/api/webhooks/whop (alternate https://brandcrew.vercel.app/api/webhooks/whop) for payment.succeeded, membership.activated, and membership.deactivated. Cancel/deactivate drops the workspace to Free when that membership matches the current plan (a stale Pro cancel after an Ultra upgrade is ignored). Mock billing still applies plans without payment when neither Whop nor Stripe is configured.GET /jobs) or when /api/cron/jobs is called with CRON_SECRET. Vercel Hobby cron is daily (0 12 * * *) — not an always-on worker. Times are 09:00 UTC.The desk picker shows marketing names only. Job runtime always calls cheaper real models so $20 plans last. Users never see provider ids in the picker. Logs and Founder Admin HQ may show displayName + providerModelId.
Single catalog: src/lib/model-catalog.ts.
| UI display name | Catalog id | Backend class | Real API model (default) |
|---|---|---|---|
| Opus 4.8 | opus-4.8 | Haiku | claude-haiku-4-5 (ANTHROPIC_DRAFT_MODEL) |
| Fable 5.1 | fable-5.1 | Sonnet | claude-sonnet-5 (ANTHROPIC_FINAL_MODEL) |
| GPT Astra | gpt-astra | GPT Terra (cheap OpenAI) | gpt-4o-mini (OPENAI_DRAFT_MODEL) |
| Gemini 3.8 Flash | gemini-3.8-flash | Gemini Flash | gemini-2.5-flash (GEMINI_DRAFT_MODEL) |
GPT Terra is the cheap OpenAI backend behind GPT Astra (gpt-4o-mini). Gemini 3.8 Flash maps onto the Flash class already in the repo (gemini-2.5-flash). Opus is never called — even if ANTHROPIC_BOOST_MODEL names an Opus id, the router falls back to Sonnet.
When the UI does not pick a model (Auto):
| Task | Backend |
|---|---|
| Free / Pro (any task) | Cheapest live: Gemini Flash if GEMINI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY is set, else gpt-4o-mini. Never Sonnet. |
| Research / outreach drafts / WhatsApp / summaries / website (Pro Plus / Ultra) | Gemini Flash |
| Classification / selector guess | Cheapest live engine (Flash → Terra → Haiku) |
| Structured JSON / short tools (planner, Pro Plus / Ultra) | Haiku |
| Real code / complex apps (Pro Plus / Ultra) | Sonnet |
| Ultra plan or Boost | Sonnet max (ANTHROPIC_BOOST_MODEL or Sonnet; never Opus) |
Hard stop: assertWorkspaceBudget before a job is queued (tokens + jobs/hour + concurrent). assertLlmCallBudget before every LLM call (tokens + suspended). Crossing tokenBudget returns BUDGET (402) and the desk shows the stop dialog. Usage estimateUsd is a stub — not a provider bill.
Desk chat: a normal question (“what is our ICP?”, Roman Urdu, or another language) gets one cheap reply in the thread (Brand Kit context, counted against the workspace token budget). Agents mirror the user’s language — including Urdu — and do not claim English-only. Explicit generate chips / playbooks still call createJobFromChat.
Keys stay on the server: GEMINI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY. Vercel Production must set GEMINI_API_KEY so Free-plan Auto and paid-key failures use Gemini Flash instead of offline templates. No keys → labeled offline templates. If a preferred provider errors (invalid OpenAI key, 429, outage), llm.complete retries remaining cheap backends starting with Gemini Flash.
Keep useful work on APIs while driving spend toward zero. There is no unlimited plan.
| Control | How it works |
|---|---|
| Action cache + routines | Successful browser_click / browser_type selectors are stored per workspace+domain. Repeat routine runs skip the LLM locator (Stagehand-style) and only call a model on cache miss or write_artifact. Save a finished job as a skill/routine (POST /api/workspaces/:id/routines) with a cadence. |
| DOM-first browse | Perception is a text DOM digest (ARIA + visible text). Screenshots are for humans / session replay — never the default model input. Vision is opt-in fallback when the digest is empty. |
| Model routing | Free/Pro → Flash or gpt-4o-mini (never Sonnet). Classify/locator → cheapest live engine. Pro Plus / Ultra writes stay on Flash/Haiku; code on Sonnet. User-facing free plan is Free (internal id demo). |
| Prompt caching | Anthropic system prompts use cache_control=ephemeral. OpenAI/Gemini keep a stable system prefix (automatic/implicit cache). |
| Credits | Token budget 1:1 as credits. Free/Pro/Pro Plus/Ultra are all capped. |
| Event triggers | Schedule uses existing cron/desk load. Email-received polls Connected Gmail. Slack mention is POST /api/workspaces/:id/triggers/fire (no Events API fleet). |
| Session replay | Finished jobs pack plan + events + sources + cost (GET .../jobs/:jobId/replay). Cheaper than live view as the headline. |
| Guards | Page text is CINEM_UNTRUSTED_PAGE_CONTENT (data only). Writes pause for approval. Domain allowlist aborts off-host. Slack/email deliver after approval; Gmail never sends. |
npm run test:cost is the fixture: a weekly-client-brief replay skips the LLM for the majority of steps once selectors are cached.
Founder Admin HQ lives at /admin (path-based internal ops console, not a customer product). Access is emails in ADMIN_EMAILS (always includes [email protected] and [email protected]). Set ADMIN_EMAILS on Vercel to every staff email or they get 403. Non-admins get 403. Plan assign / revoke / suspend / budget and flag writes are audited in AdminAuditLog. Download backup (GET /api/admin/backup) is a redacted JSON copy for a founder PC — live truth stays on cloud Postgres. See docs/admin-ops.md.
/admin is CINEM staff only. Same app, strict email gate. Subdomain admin.* is not wired.
| Section | What it shows (Postgres / env, never fake KPIs) |
|---|---|
| Overview | User count, paid vs free workspaces, by-plan counts, jobs running / needs_you / failed 24h / created 24h, token + chat sums, Support + webhook counts, recent failures, approvals. Download backup |
| Customers 360 | Recent users or email search → workspaces, plan, tokens, memberships, recent jobs, usage events. Assign / revoke / suspend / set budget |
| Billing | Paid workspaces, Whop membership id, BrandSupport tips, ProcessedWebhook events. No invented credit balances |
| Model / cost | Display→backend map, provider key present/absent (booleans only), UsageEvent totals by model |
| Access | Effective admin emails from env (local part masked, domain visible). Role is superadmin via ADMIN_EMAILS only. SSO later |
| Audit | Full AdminAuditLog with action / actor / target filters + founder backup download |
| Trust & safety | User or workspace search + revoke to Free / suspend (ban-lite) |
| Feature flags | FeatureFlag { key, enabled, note } with confirm + audit |
ADMIN_EMAILS parsing always unions [email protected] and [email protected]. Add each extra operator on Vercel (Production and Preview), comma-separated. QA accounts belong in that env var, not in source. Backup JSON is a copy for a local PC — see docs/admin-ops.md.
Google Antigravity (agent sessions / computer-use) is a follow-up — too heavy for this MVP. Website jobs use Gemini Flash when the key is present.
Replit is optional (REPLIT_CONNECT_URL). App preview is a sandboxed iframe. There is no fake Connected Replit plugin.
| Plan | Price | Seats | Tokens | Jobs/hour | Concurrent |
|---|---|---|---|---|---|
| Free | $0 | 1 | 40,000 | 6 | 1 |
| Pro | $20/mo | 2 | 50,000 | 8 | 1 |
| Pro Plus | $79/mo | 5 | 200,000 | 30 | 3 |
| Ultra | $200/mo | 12 | 600,000 | 90 | 6 |
Existing workspaces stored as growth map to Pro Plus. Token budget, hourly jobs, concurrent jobs, and seats are enforced on job create and invites. The desk header shows remaining credits (token budget 1:1).
Token budget, hourly jobs, and concurrent running jobs are enforced on job create. The desk header shows remaining caps.
npm run test:llm # routing + client boot checks (fake keys, no paid calls)
npm run test:models # display-name catalog → cheap backend ids
npm run test:onboarding # one-box wizard, OAuth return, Free plan copy
npm run test:admin # Admin HQ allow-list, masking, backup redaction, section APIs, 403 authz
npm run test:helpdesk # Help widget, English ack, Admin inbox + live handoff contracts
npm run test:jobs # playbooks, live-output gate, URL guard, browse stubs (no database)
npm run test:companions # gallery templates, allowed tools, Yes/No clarify helpers
npm run test:marketplace # catalogs, encrypt, Connect-without-key stays disconnected
npm run test:oauth # mocked Gmail/Slack token exchange + Connected persistence (DB smoke skipped if Postgres is down)
npm run test:browse # optional: Playwright against example.com (needs Chrome)
npm run test:api-router # catch-all matcher still resolves every public /api URL
npm run test:developer-api # hashed keys, catalog, JSON 401 shape
npm run test:limits # plan caps, builder playbooks, 3D avatar seed, HTML preview
npm run test:product # $20/$79/$200 plans, onboarding, templates, schedule math, export PDF
npm run test:billing # Whop-first provider, webhook signature, cancel rules
npm run test:cinem-ai # Assistant marketing + usage/upgrade contract (existing Pro plan)
npm run test:launch # logo, privacy/terms, headers, rate limit, honeypot, SEO files
npm run test:on-device # MV3 extension, native host, allowlist, write-gate, agency playbooks
npm run test:write-gate # approval class, Always approved preference, Gmail OAuth testing errors
npm run test:cost # action cache, DOM-first, cheap routing, credits/Free, triggers, replay
npm run test:phase3 # Composio disconnect honesty, memory, multi-tab, agency playbooks, client desks
npm run test:phase4 # RBAC, DPA/security docs, SOC 2 readiness (not certified), audit export hashes
# npm run composio:first-call # live SDK proof when COMPOSIO_API_KEY is set (never commit the key)
Jobs live in Postgres (Job, JobEvent, Skill, Agent). Each job has a JSON plan of steps. The runner ticks one step at a time.
v1 tools:
read_brand_kitbrowser_navigate / browser_snapshot (paired Chrome CDP first; Playwright + system Chrome when PLAYWRIGHT_ENABLED; otherwise fetch)browser_click / browser_type / browser_extract / browser_screenshot — user Chrome via the MV3 side panel, else a job-scoped Playwright tab on desktop. On Vercel without a paired device they return “needs the Chrome extension” and never fake success. Still refuse login, password fields, and send. Click/type pause unless Always approved is on.crawl_links (depth 1–2, hard cap of 4 pages per job; off-allowlist hosts abort)fetch_url (public HTTP GET, HTML→text, size-capped; localhost/private IPs blocked)web_search (Tavily; requires Connected Web Search plugin)gmail_list_recent / gmail_create_draft (Connected Gmail; draft only, never send). Drafts do not pause. Send is always gated if added later.slack_list_channels / slack_draft_message / slack_post_message (Connected Slack; post only after ask_user; Always approved does not skip posts)native_file_read / native_file_write (native messaging host; writes always pause for approval)browser_tabs (5–10 public URLs in parallel; DOM-first; allowlist; research playbooks)composio_execute (Connected Composio toolkit; writes pause; missing COMPOSIO_API_KEY stays disconnected)read_artifact (outreach pack reads the latest research/competitor artifact)write_artifact (markdown artifact on the workspace; research kinds append Sources + Uncertainty)ask_user — kind: "approve" waits for high-risk writes; kind: "clarify" waits for Yes/No (or a short answer) stored on Job.userAnswer, then resumes the same job. In-desk drafts and Gmail list do not add a trailing approve.Jobs bind to a user Agent (agentId). Activity events include { tool, url, excerpt } for browse steps. Companion allowedTools (JSON on Agent) can restrict which tools that companion may run.
CINEM Pro does not spin a VM per agent and does not require a paid browser vendor.
Where the browser runs
| Environment | Navigate / snapshot / crawl | Click / type / extract / screenshot |
|---|---|---|
| Paired CINEM Pro Chrome extension | User Chrome via CDP (chrome.debugger). Domain allowlist enforced. | Same tab. Writes still pause for approval. |
npm run dev or Electron (no extension online) | Playwright against system Chrome when PLAYWRIGHT_ENABLED and Chrome is found; otherwise fetch | Live Playwright tab per job. Closed when the job finishes or sits idle ~10 minutes. |
| Vercel Hobby, no paired device | Fetch fallback (no Chrome on serverless). PLAYWRIGHT_ENABLED defaults off. | Honest desktop_required unless the MV3 extension is paired and polling. |
Electron also starts native-host/host.mjs --http on 127.0.0.1:43181 for files / keepalive. Browserbase / a remote Chrome worker / a Chromium fork is out of scope.
Local test
npm install. Playwright core uses the Chrome already on your machine.PLAYWRIGHT_ENABLED=true in .env. Custom binary: PLAYWRIGHT_CHROME_PATH.npm run dev (or npm run desktop:dev). Download the extension zip from On-device Chrome, Load unpacked, pair, then Marketplace → Companions → add Prospect Peter (or New Agent with browser tools).npm run test:browse — Playwright against example.com when Chrome is present.npm run test:companions, npm run test:jobs, and npm run test:on-device — playbooks, allowlist, write-gate, gallery (no database).Limits this phase: no auto-login, no password automation, no LinkedIn send, no file downloads, max 4 pages/job. Gmail creates drafts only. Slack posts only after you approve. Live keys never persist canned browse copy — if the model fails, the artifact is the captured page text. QuickBooks write on the invoice finder is labeled TODO.
See Survival limits above. Crossing the token budget, hourly job cap, or concurrent-job cap returns a hard stop.
npm run dev # ensure env + db, then Next.js on :43180
npm run build
npm run start
npm run lint
npm run test:jobs
npm run test:companions
npm run test:oauth
npm run test:browse # Playwright smoke test (Chrome + network)
npm run test:api-router
npm run test:developer-api
npm run test:limits
npm run test:billing
npm run test:launch
npm run test:on-device
npm run desktop:cloud # Electron window against https://app.cinem.tech
npm run desktop:dev # Electron window against local Next (:43180)
npm run desktop:build:win
npm run desktop:build:mac # needs macOS
npm run db:up # docker compose Postgres
npx prisma migrate deploy
npx prisma studio # inspect rows
Per-agent VMs, Chromium forks, auto-login browse, auto-post to LinkedIn/Meta, auto WhatsApp/Gmail send, meeting transcription, 93 integrations, own LLM, unlimited plans, full CRM, user-managed LLM keys, mobile apps, claiming “fully autonomous” or feature-complete parity with Strawberry. Fundraising banner is out of this PR.
GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET | no | Optional Gmail-specific OAuth overrides. |
OAUTH_REDIRECT_BASE / APP_URL | no | OAuth callback origin. Falls back to NEXT_PUBLIC_APP_URL then http://127.0.0.1:43180. |
SLACK_CLIENT_ID / SLACK_CLIENT_SECRET | no | Slack OAuth. Missing → Connect stays disconnected. |
NOTION_CLIENT_ID / NOTION_CLIENT_SECRET | no | Notion OAuth. |
GITHUB_TOKEN | no | Optional GitHub plugin env; or paste a PAT in Connect. |
BILLING_MOCK | no (defaults true when neither Whop nor Stripe is set) | Apply Pro/Pro Plus/Ultra locally without a payment provider. |
BILLING_PROVIDER | no | Optional force: whop, stripe, or mock. Default prefers Whop, then Stripe, then mock. |
WHOP_API_KEY | no | Whop Account API key (apik_ / whop_). Enables live Whop checkout. |
WHOP_COMPANY_ID | no | Business id (biz_…). Required for live checkoutConfigurations.create (company_id / plan.company_id). Alias: WHOP_ACCOUNT_ID. |
WHOP_WEBHOOK_SECRET | no | Signing secret (ws_…) for POST /api/webhooks/whop. |
WHOP_STARTER_PLAN_ID / WHOP_PRO_PLAN_ID / WHOP_ULTRA_PLAN_ID | no | Existing Whop plan ids. If unset, checkout creates a $20 / $79 / $200 monthly renewal. |
WHOP_ASSISTANT_PRODUCT_ID / WHOP_ASSISTANT_MONTHLY_PLAN_ID / WHOP_ASSISTANT_3MO_PLAN_ID / WHOP_ASSISTANT_6MO_PLAN_ID / WHOP_ASSISTANT_1YR_PLAN_ID | no | Cinem AI Assistant standalone billing (/cinem-ai-assistant/billing). Required for live checkout — see docs/cinem-ai-assistant.md. |
WHOP_SUPPORT_PRODUCT_ID / WHOP_SUPPORT_PLAN_ID | no | Support (tips) product/plan. Checkout always sends a one-time initial_price of $1–$99,999. See docs/whop-support.md. |
WHOP_SANDBOX | no | true sends API calls to sandbox-api.whop.com. |
STRIPE_SECRET_KEY | no | Stripe Checkout fallback when Whop is not configured (and optional Stripe plugin env). |
STRIPE_STARTER_PRICE_ID / STRIPE_PRO_PRICE_ID / STRIPE_ULTRA_PRICE_ID | no | Stripe price IDs for $20 / $79 / $200 plans. STRIPE_GROWTH_PRICE_ID is accepted as a Pro Plus alias. |
CRON_SECRET | no | Bearer secret for GET /api/cron/jobs. If unset, schedules still run when the desk loads. |
NEXT_PUBLIC_APP_URL | no | Checkout + OAuth redirect origin. |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY / STRIPE_WEBHOOK_SECRET | no | Reserved for Stripe test-mode. |
NEXT_PUBLIC_GA_ID | no | Optional GA4 id. Script loads only after cookie Accept. |
NEXT_PUBLIC_PLAUSIBLE_DOMAIN | no | Optional Plausible domain. Script loads only after cookie Accept. |
AZURE_TENANT_ID / AZURE_CLIENT_ID / AZURE_CLIENT_SECRET | no (Windows packager) | Entra app for Azure Artifact Signing. Empty → unsigned Setup.exe. See docs/windows-code-signing.md. |
AZURE_TRUSTED_SIGNING_ENDPOINT / AZURE_TRUSTED_SIGNING_ACCOUNT_NAME / AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME / AZURE_TRUSTED_SIGNING_PUBLISHER_NAME | no (Windows packager) | Artifact Signing profile (names, not secrets). Endpoint must match the account region. |
CSC_LINK / CSC_KEY_PASSWORD | no (Windows packager) | Alternate OV/EV Authenticode PFX. Never commit the .pfx. |
| Spam protection on public forms | Done (honeypot company_url + IP rate limit) |
| Analytics hook | Done (NEXT_PUBLIC_GA_ID or NEXT_PUBLIC_PLAUSIBLE_DOMAIN; no script if unset) |
| Landing CTA Open desk / Get started | Already preserved |
| Security headers (CSP, HSTS, XFO, nosniff, referrer, permissions) | Done |
Admin routes ADMIN_EMAILS gated | Already (requireAdmin / loadAdminPage on every page + API; Settings only hides the link) |
| Session tokens in HttpOnly cookies | Already brandcrew_session; this pass audits no localStorage session JWT + Secure on Vercel |
| Google email verification | Already callback bounce; Done email_verified === true (missing field is unverified) |
| Email/password verification / 2FA | Documented — no mailer, no fake 2FA; Google is the verified-email path; 2FA follow-up |
| Password rules | Done (min 8, trivial list, optional HIBP fail-open on signup + password change) |
| Rate limit login/signup/OAuth/checkout/admin | Done (per IP + per email on login/signup; tighter admin POST; PATCH /api/auth/me) |