Commit Graph

81 Commits

Author SHA1 Message Date
73b672f2c9 fix(anatomy): harvest inner-app fqdns + prioritise custom domains
Coolify stores user-facing domains on the inner application of a
service (e.g. Twenty's `twenty` app), not on the parent service. The
anatomy endpoint was reading the service-level fqdn and getting null,
so live URL chips never rendered.

- smartServiceMetaFor (replaces smartServiceStatusFor): collects
  fqdns from non-excluded inner apps in addition to status
- prioritiseFqdns: pushes auto-generated *.sslip.io / *.coolify.app
  URLs to the back so real custom domains surface first
- fqdnsOf: strips default ports (443, 80, container 3000) so chips
  link to the public Traefik-served URL, not the internal port

Made-with: Cursor
2026-04-30 13:45:06 -07:00
60a04e48c1 feat(plan): Objective/Sessions/Tasks tab with markdown + AI scribe
- Objective: full markdown document editor with Write/Preview tabs
- Sessions: project-scoped chat threads with AI-generated summaries
- Tasks: master-detail view with markdown spec, status pills, agent
  delegation placeholder
- Chat threads now scoped per-project and auto-summarised after each
  assistant turn (powers Sessions list)
- AI MCP scribe tools: plan_get / plan_vision_set / plan_idea_add /
  plan_task_add (title + markdown desc) / plan_task_complete /
  plan_decision_log
- Chat panel clears stale project threads when navigating to workspace

Made-with: Cursor
2026-04-30 13:44:50 -07:00
5ecb0349d7 feat(plan): add Plan tab as the first project surface
A new home for everything that happens BEFORE building:
- Vision    — one-line elevator pitch (mirrors productVision)
- Ideas     — the "park-it" bin for raw thoughts
- Tasks     — what needs to happen next (open / done)
- Decisions — log of "we chose X over Y because Z"

Storage is appended under fs_projects.data.plan so no schema migration
is needed. CRUD lives at /api/projects/[projectId]/plan.

The bare project URL now redirects to /plan instead of /product, and
the AI chat receives decisions + open tasks in its active-project
context block — so it stops re-litigating settled questions and knows
what's queued up.

Made-with: Cursor
2026-04-29 18:02:02 -07:00
b6eaa85733 fix(tenancy): stop leaking workspace-level Coolify services across projects
CRITICAL: every Vibn project was rendering every other project's
services in the same workspace (Twenty CRM, n8n, all databases,
all secrets). Tenancy was effectively broken — cross-project data
exposure inside a workspace.

Root cause:
  - Coolify's POST /projects validates `description` against a strict
    allowlist (letters, numbers, spaces, and `- _ . , ! ? ( ) ' " + = * / @ &`).
  - Our description "Vibn project: <name> (workspace: <slug>)" contains
    two colons. Every project-create on Coolify returned 422.
  - lib/projects.ts caught that 422 and fell back to
    `workspace.coolify_project_uuid` so deploys "weren't blocked."
  - That UUID is shared by every Vibn project in the workspace, so
    listServicesInProject(coolifyProjectUuid) returned the union of
    all projects' services, applications, and databases for any
    project in the workspace. The Product, Hosting, and Infrastructure
    tabs all rendered cross-tenant data as if it were the current
    project's.

Fixes (defense in depth — fix at every layer):

  1. lib/coolify.ts createProject(): sanitize the description against
     Coolify's allowlist at the boundary so no caller can ever ship
     a description that 422s. Replaces disallowed chars with `-`,
     collapses runs, caps at 255 chars.

  2. lib/projects.ts ensureProjectCoolifyProject():
     - Pre-sanitize the description we pass (belt + suspenders).
     - Detect when `stored === workspace.coolify_project_uuid` (the
       legacy bad state) and re-provision a dedicated project.
     - REMOVE the workspace-UUID fallback on create failure. A 422
       now leaves coolifyProjectUuid null and the UI shows an empty
       state, which is correct: better to surface "no resources" than
       to lie about which project owns what.
     - Export sanitizeCoolifyDescription helper for reuse.

  3. /api/projects/[projectId]/anatomy/route.ts: SELF-HEAL on every
     read. If the project's stored Coolify UUID matches the
     workspace's UUID, we treat it as missing, re-provision a
     dedicated Coolify project on the fly (idempotent — reuses the
     existing one if found by name), persist the new UUID, and
     continue serving with the corrected scope. If provisioning
     fails we fall back to undefined, NOT the workspace UUID, so
     no cross-tenant data ever surfaces again.

The self-heal means existing already-broken projects will fix
themselves on the next page load — no manual data migration needed.

Made-with: Cursor
2026-04-29 17:16:33 -07:00
90bed6ab31 feat(github): OAuth integration + repo picker for Import flow
User can now click "Connect GitHub" inside the Import-existing-code
flow, sign in via GitHub, and pick a repo from a searchable list of
their own + collaborator + org repos. Both public and private repos
work — the encrypted access token on the user's account is auto-
attached when the create endpoint runs the agent-runner mirror.

OAuth flow:
  - GET  /api/integrations/github/connect    — generates state, sets
         a 10-min httpOnly cookie, 302s to GitHub authorize.
  - GET  /api/integrations/github/callback   — verifies state,
         exchanges code for token, fetches /user, encrypts the
         token with secret-box (AES-256-GCM, VIBN_SECRETS_KEY) and
         persists it on fs_users.data.integrations.github.
         Bounces back to ?gh_connected=login or ?gh_error=msg.
  - GET  /api/integrations/github/repos      — server-side fetches
         the connected user's repos (per_page=100, sort=pushed,
         affiliation=owner+collaborator+org_member). Returns the
         GitHub login + a stripped repo summary; never the token.
  - POST /api/integrations/github/disconnect — drops the integration
         from fs_users (does NOT revoke on github.com).

Scopes requested: repo, read:user.

Token storage:
  - Encrypted at rest with secret-box (lib/auth/secret-box.ts) using
    VIBN_SECRETS_KEY. Tokens never leave the server.
  - One token per fs_users row, keyed by email.

ImportSetup UI:
  - On mount, fires /repos to detect connection state.
  - If connected: shows a connected-as-@login chip with disconnect
    link, a search-as-you-type repo picker (max 220px scroll, badges
    for Private / language), and a "paste a different URL instead"
    escape hatch.
  - If not connected: shows a Connect GitHub card with a public-URL
    fallback inline.
  - On return from OAuth (?gh_connected=… or ?gh_error=…), surfaces
    a toast and silently refreshes the repo list.
  - Selected repo carries default_branch + repo id into the create
    payload so we can store them on the project for later UI hints.

/api/projects/create:
  - When a githubRepoUrl is mirrored, falls back to the user's
    OAuth-linked token if no PAT is explicitly passed. Means the
    flow "just works" for private repos once GitHub is connected.

Required env (already set in production):
  - GITHUB_CLIENT_ID
  - GITHUB_CLIENT_SECRET

Made-with: Cursor
2026-04-29 16:44:13 -07:00
c7bb0eea58 feat(project-creation): replace owner-style picker with audience picker
"Myself / A client" was about who *owns* the project (a billing
concern), but at creation time we want to know who *uses* it — that's
what determines which Infrastructure providers we should pre-stage.

  team       = internal users (your team / employees)
               → SSO-style auth, no payments by default, simple roles
  customers  = external users (the public)
               → public sign-up + payments + transactional email by
                 default, custom domain matters

Both choices are reversible from the Infrastructure tab later — the
selector copy makes that explicit so users don't feel locked in.

Changes: - setup-shared: ForWhomSelector ("Myself" / "A client") replaced by
    AudienceSelector ("My team" / "Customers"), with an "you can
    change this later" hint underneath. New Audience union type
    exported for the three setup screens to share.
  - BuildSetup / OssSetup / ImportSetup: swap state + import + payload.
    Defaults: BuildSetup → customers (most "vibe coder" projects are
    public products), ImportSetup → customers (existing repos usually
    are too), OssSetup → team (Twenty / n8n / Plausible style tools
    are most often deployed for internal use).
  - /api/projects/create: drop isForClient (we never read it
    anywhere), persist audience as a first-class field on the
    project record so the AI can branch on it during the first chat.
Made-with: Cursor
2026-04-29 16:24:54 -07:00
7a9cd68ea8 feat(project-creation): 3-path wizard — Build / OSS / Import
User feedback: the previous flow was a single-screen "name + audience"
dialog that gave AI no context about what the user actually wanted to
make. That worked for the demo but produced messy projects in practice
because everything was decided after the fact in chat.

The new flow asks the user one human question first ("How would you
like to begin?") and then captures the minimum context needed to seed
the AI's first conversation in the project.

Three paths, each is a 2-step setup screen with internal step dots:

  - Build your own idea  — Step 1: name + audience.  Step 2: free-text
    "what do you want to build". Becomes the project's vision and the
    AI's first-message context.

  - Run an open source tool  — Step 1: name + audience.  Step 2:
    segmented tabs to either (a) paste a GitHub link or (b) describe
    the kind of tool you want and have Vibn find one. Vision is set
    to either "Install and host this open-source project: <url>" or
    "Find and install an open-source tool that fits this need: <desc>"
    so the AI knows which mode to operate in on first chat.

  - Import existing code  — Step 1: name + audience + repo URL.
    Step 2: optional "what do you want to do with it" textarea.
    Public repos only for v1; private-repo OAuth lands later.

Backend:
  - /api/projects/create now accepts and persists `creationMode` and
    `sourceData` on the project record under a `kickoff` blob:
      { mode, sourceData, vision, createdAt }
    The chat endpoint will read this on first turn to seed the AI
    with the user's stated intent rather than asking them to re-type
    it in chat.

Cleanup:
  - Removed FreshIdeaSetup, CodeImportSetup, ChatImportSetup,
    MigrateSetup — replaced by BuildSetup, OssSetup, ImportSetup.
  - Removed the unused initialWorkspacePath prop from
    project-association-prompt (the new flow doesn't take it).
  - TypeSelector defaults are restored — the modal opens on the
    type-picker step now, not directly on a setup form.

UI building blocks added to setup-shared:
  - TextArea (multi-line input)
  - StepDots (page indicator)
  - SegmentedTabs (generic-typed tab selector, used in OSS Step 2)
  - SecondaryButton (used as ← Back inside Step 2)

Made-with: Cursor
2026-04-29 16:16:53 -07:00
7b359e399e feat(infra): collapse to 7 categories + live Postgres table inspection
UX rework after iteration with the user:

  - Drop SMS, Analytics, Search, Monitoring categories from the rail.
    They were detection-only with no first-class UX behind them; surface
    is cleaner without them and they can return when each gets real
    flows (auth-style "edit configurables", payment-style "connect").
  - Storage no longer tries to detect S3/R2/GCS env vars. Instead it
    surfaces the workspace's bundled Vibn-provisioned GCS bucket
    (S3-compatible HMAC), with status, region, access id, and a
    one-shot env snippet for app config.
  - Email category no longer mixes in SMS providers.
  - LLM renamed to "Models"; empty state mentions BYOK as upcoming.
  - Payments empty state has a "Connect Stripe (coming soon)" CTA;
    Stripe detail surfaces the webhook URL guidance.
  - Secrets detail now lists actual env-var key names per resource,
    grouped by detected provider (Stripe block, OpenAI block, etc.)
    with an "Other (project-defined)" catch-all. Each row has Edit +
    Rotate icon buttons (currently disabled with tooltips — wire-up
    to apps.envs.upsert / services.envs.upsert lands in iter 2).

Live database inspection (Postgres only for now):

  - New /api/projects/[id]/databases/[uuid]/tables — auth-scoped, lists
    user-tables across non-system schemas via SSH-exec into the
    database container's psql. Hard caps: 50 tables, 8s timeout, no
    mutating queries possible (only SELECT row_to_json with LIMIT).
  - New /api/projects/[id]/databases/[uuid]/preview — returns first 50
    rows of a single table. Identifiers locked to /[A-Za-z0-9_]+/ so
    splicing them into the SELECT is safe.
  - DatabaseTableTree (lazy-fetch, schema-grouped, public-flat,
    approximate row counts from pg_class.reltuples) and TableViewer
    (sticky-header data grid, zebra rows, per-cell ellipsis at 360px).
  - Fix in lib/coolify.ts: listDatabasesInProject was flattening every
    db endpoint array (postgresqls, redises, mongodbs…) without
    tagging the output rows with the engine. Every consumer was
    seeing type=undefined which then bucketed as "unknown" and
    blocked the table inspector. Now we tag at the flatten step so
    every CoolifyDatabase has a stable type.
  - Infrastructure tab: database tile is now expandable inline like
    Codebases on Product. Auto-expands the first DB; click any table
    to preview rows on the right.

Made-with: Cursor
2026-04-29 15:22:58 -07:00
63f18d46a5 feat(project): wire Infrastructure tab to live Coolify data
Three sub-areas, all real, no static placeholders:

  Databases — listDatabasesInProject(coolifyProjectUuid). Type is
              normalised (postgresql / redis / mongodb / mysql / keydb
              / dragonfly / clickhouse) so the tile subtitle is stable
              regardless of how Coolify spells the engine.

  Providers — auto-detected from env-var keys across every app + service
              in the project. 35+ patterns covering Auth (Clerk, Auth0,
              Supabase, NextAuth, SuperTokens, WorkOS, Firebase Auth),
              Email (Resend, Mailgun, Postmark, SendGrid, SES, Loops),
              SMS (Twilio, Vonage), Payments (Stripe, LemonSqueezy,
              Paddle), Analytics (PostHog, Mixpanel, Amplitude, Plausible,
              Umami), LLM (OpenAI, Anthropic, Google AI, Mistral, Cohere,
              Groq, OpenRouter), Storage (S3, R2, GCS, Supabase),
              Search (Algolia, Meilisearch, Typesense), Monitoring
              (Sentry, Datadog, LogSnag). Each tile drills down to show
              which app/service the keys live in and which keys matched.

  Secrets   — env-var totals per app/service, sorted by count. Values
              are never read or returned from this surface — keys only.
              The detail pane explains how to read/edit (via AI chat
              with services.envs.* / apps.envs.* MCP tools).

Anatomy endpoint extended in the same single-fetch shape: env vars are
loaded once, then both detectProviders() and summariseSecrets() run
against that one source so we don't double-fetch.

The static What-lives-here grid is gone — every tile shown corresponds
to something that actually exists in the project.

Made-with: Cursor
2026-04-29 14:42:23 -07:00
307c3ca858 feat(project): unify Product+Hosting around code/images and live/previews
Anatomy + UI rewrite — locked the conceptual model after user feedback:

Product = "what makes up the thing you're shipping":
  - Codebases (Gitea repos)
  - Images (Coolify services backed by upstream Docker images: Twenty
    CRM, n8n, etc.)
  - Dev containers no longer surface here. The vibn-dev-* container is
    the AI's workshop, not a product surface; previews it serves still
    appear under Hosting → Previews.

Hosting = "where it lives + how it gets there", unified:
  - Live: every running endpoint as one list. Each item carries a
    source badge ("repo" | "image"), status dot, attached domain, and
    last-build summary inline. No separate Build, Domains or Services
    categories — those are properties on each Live item.
  - Previews: dev container preview URLs (unchanged).

Anatomy endpoint reshaped accordingly:
  - product.{codebases, images}
  - hosting.{live, previews}  (was production/services/previewUrls/domains)
  - lastBuild summary fetched per repo-app via listApplicationDeployments
    in parallel.

ProjectStagePill rewired to derive Live/Down/Building from hosting.live
+ hosting.previews. dev-container-detail.tsx removed.

services.* MCP tools added so AI agents can manage Coolify services
(Twenty CRM, n8n, …) the same way they manage apps:
  - services.list, services.get
  - services.start, services.stop
  - services.envs.list, services.envs.upsert
All tenant-scoped via getServiceInWorkspace + getOwnedCoolifyProjectUuids.
vibn-dev-* containers stay hidden from services.list.

Made-with: Cursor
2026-04-28 19:36:35 -07:00
3db7191146 feat(project): split dev containers into Product; convert Hosting to tile-rail
The vibn-dev-* services that the AI authors code in conceptually
belong to Product (build surface), not Hosting (runtime + reach).
Anatomy endpoint now splits Coolify services by name prefix:
  - vibn-dev-* → product.devContainers[]
  - everything else → hosting.services[]

Product tab gains a "Workspace" section above the codebases stack
with a single dev-container tile. Selecting it shows status +
active dev servers in the right pane. Codebase + file selection
behaves the same as before.

Hosting tab restructured from a stack of always-visible cards to
the same tile-rail pattern Product uses: left rail has 4 always-
present categories (Production / Services / Previews / Domains)
each with a count badge, items inside are clickable tiles, right
pane shows details for the selected item. Empty categories show a
one-liner explaining what would appear there — teaches the user
the model on a brand-new project without being preachy.

Made-with: Cursor
2026-04-28 18:54:19 -07:00
6fca78dca9 feat(project): unified anatomy endpoint + live Hosting tab + truthful Live pill
Adds GET /api/projects/[id]/anatomy returning the full project shape
in one shot — codebases (Gitea), production apps (Coolify
applications matched by repo URL), dev services (Coolify services in
the project's coolifyProjectUuid), preview URLs (active fs_dev_servers
rows), and aggregated domains. Each tab reads its own slice via the
new useAnatomy() hook so the page never fans out 3+ requests.

Hosting tab is now real: surfaces production / dev services / preview
URLs / domains with empty-state CTAs explaining what each means and
why it's empty when applicable. Includes a banner when nothing at all
is deployed for the project.

Project header pill (previously hard-coded from data.status, which
historically lied) now derives stage from hosting reality:
  - any production app running → Live (green)
  - any failed app             → Down (red)
  - any service / preview      → Building (blue)
  - else                       → fallback to data.status

Product tab refactored onto the same useAnatomy hook so we no longer
maintain two near-identical fetchers.

Made-with: Cursor
2026-04-28 17:38:57 -07:00
6e4e9c02ff feat(project): auto-discover codebases from Gitea instead of hard-coding
Adds GET /api/projects/[id]/codebases that inspects the project's
Gitea repo:
  - apps/* present → one codebase per subdir (Turborepo)
  - else          → single codebase rooted at the repo root
  - no repo       → empty list with reason="no_repo"

Product tab now fetches this list, picks the first as the default
selection, and surfaces explicit loading / error / empty states
(previously it hung on "Loading…" when apps/web 404'd in single-
repo projects).

Made-with: Cursor
2026-04-28 16:49:34 -07:00
2de3c5ce57 fix(backfill): move endpoint to /api/admin/ to avoid [projectId] catch-all
Made-with: Cursor
2026-04-27 19:45:06 -07:00
a83cc45f6a feat(backfill): support ops-secret bootstrap auth for backfill-isolation
Made-with: Cursor
2026-04-27 19:39:04 -07:00
769fbdcba2 feat(mcp): per-resource Vibn-project ownership + backfill endpoint
Stage 3 of per-project Coolify isolation. Adds an authoritative ownership
table so apps_list { projectId } returns ONLY the resources actually owned
by that Vibn project, even when multiple Vibn projects share a single
Coolify project (the legacy workspace-level vibn-ws-{slug}).

- New table fs_project_resources (project_id, resource_uuid, type, workspace).
  Auto-created on first use.
- lib/projects.ts: linkResourceToProject / unlinkResource /
  getProjectResourceUuids / getProjectIdForResource helpers.
- apps_list { projectId }: when the project's coolifyProjectUuid equals the
  legacy workspace project, restrict results to explicitly-linked resources.
  When it has a dedicated Coolify project, return everything in that project.
- apps_create / databases_create: auto-link the newly-created resource to
  the requesting Vibn project.
- apps_delete / databases_delete / services_delete: unlink on success.
- projects_get → possibleDeployments: prefer explicit links; fuzzy-match
  fallback only fires when no link table entry exists yet.
- POST /api/projects/backfill-isolation: idempotent migration that mints a
  dedicated Coolify project for every Vibn project AND records existing
  coolifyServiceUuid/coolifyAppUuid/coolifyDatabaseUuid links. Resolves
  the "Twenty CRM project shows n8n" bug for legacy projects without
  needing to physically move services in Coolify.

Made-with: Cursor
2026-04-27 19:33:07 -07:00
b9c8457eb3 Stop auto-scaffolding 4 sub-apps + turborepo on project creation
Every new Vibn project was being seeded with:
  - a turborepo scaffold pushed to its Gitea repo
    (apps/product, apps/website, apps/admin, apps/storybook)
  - 4 corresponding Coolify services that nobody ever deployed

Both predate templates / GitHub imports / on-demand AI deploys and
created noise in every workspace's Coolify view (and confused the AI
about what was actually running).

Now project creation provisions just:
  - a Gitea repo (empty unless GitHub mirror is requested)
  - a dedicated Coolify project ready to receive deploys

Apps land in the project via apps_create on demand — what the user
actually wants, not a guess. The lib/scaffold/turborepo/ files remain
in source for future opt-in re-introduction.

Made-with: Cursor
2026-04-27 19:06:47 -07:00
1a686c2a23 Per-project Coolify project isolation (Stage 1)
Each Vibn project now gets its OWN Coolify project named
vibn-{workspace-slug}-{project-slug}. All apps/databases/services
deployed for the project land inside that Coolify project, giving
us clean grouping, cascading delete, and per-project domain
namespaces.

Changes:
- New lib/projects.ts: ensureProjectCoolifyProject (idempotent
  create/lookup), getProjectCoolifyUuid, getOwnedCoolifyProjectUuids
- /api/projects/create: pre-insert row, mint per-project Coolify
  project, then complete the row with productData (preserves the
  coolifyProjectUuid that was just set)
- apps.list (MCP): without projectId, aggregates across ALL
  workspace-owned Coolify projects; with projectId, scopes to
  that project's Coolify project. Returns coolifyProjectUuid
  on each result so the AI knows where things live.
- apps.create (MCP): accepts projectId; auto-mints the Vibn
  project's Coolify project on first deploy if missing
- apps_list/apps_create tool defs: projectId param surfaced
- System prompt: Project as first-class — planning + live as
  facets of ONE thing, never as separate worlds. AI told to
  always pass projectId on apps_create.

Stage 2 (next): set-aware ensureResourceInProject across all
single-resource MCP tools (apps.get/delete/exec/etc.) and
cascading delete via projects.delete.

Made-with: Cursor
2026-04-27 19:02:43 -07:00
651ddf1e11 Rip out Theia, ship P5.1 attach E2E + Justine UI work-in-progress
Theia rip-out:
- Delete app/api/theia-auth/route.ts (Traefik ForwardAuth shim)
- Delete app/api/projects/[projectId]/workspace/route.ts and
  app/api/projects/prewarm/route.ts (Cloud Run Theia provisioning)
- Delete lib/cloud-run-workspace.ts and lib/coolify-workspace.ts
- Strip provisionTheiaWorkspace + theiaWorkspaceUrl/theiaAppUuid/
  theiaError from app/api/projects/create/route.ts response
- Remove Theia callbackUrl branch in app/auth/page.tsx
- Drop "Open in Theia" button + xterm/Theia PTY copy in build/page.tsx
- Drop theiaWorkspaceUrl from deployment/page.tsx Project type
- Strip Theia IDE line + theia-code-os from advisor + agent-chat
  context strings
- Scrub Theia mention from lib/auth/workspace-auth.ts comment

P5.1 (custom apex domains + DNS):
- lib/coolify.ts + lib/opensrs.ts: nameserver normalization, OpenSRS
  XML auth, Cloud DNS plumbing
- scripts/smoke-attach-e2e.ts: full prod GCP + sandbox OpenSRS +
  prod Coolify smoke covering register/zone/A/NS/PATCH/cleanup

In-progress (Justine onboarding/build, MVP setup, agent telemetry):
- New (justine)/stories, project (home) layouts, mvp-setup, run, tasks
  routes + supporting components
- Project shell + sidebar + nav refactor for the Stackless palette
- Agent session API hardening (sessions, events, stream, approve,
  retry, stop) + atlas-chat, advisor, design-surfaces refresh
- New scripts/sync-db-url-from-coolify.mjs +
  scripts/prisma-db-push.mjs + docker-compose.local-db.yml for
  local Prisma workflows
- lib/dev-bypass.ts, lib/chat-context-refs.ts, lib/prd-sections.ts
- Misc: stories CSS, debug/prisma route, modal-theme, BuildLivePlanPanel

Made-with: Cursor
2026-04-22 18:05:01 -07:00
14835e2e0a Revert "fix(gitea-bot): add write:organization scope so bot can create repos"
This reverts commit 6f79a88abd.

Made-with: Cursor
2026-04-21 11:12:20 -07:00
6f79a88abd fix(gitea-bot): add write:organization scope so bot can create repos
Without this the bot PAT 403s on POST /orgs/{org}/repos, which is
the single most important operation — creating new project repos
inside the workspace's Gitea org.

Made-with: Cursor
2026-04-21 11:05:55 -07:00
acb63a2a5a feat(workspaces): per-account tenancy + AI access keys + Cursor integration
Adds logical multi-tenancy on top of Coolify + Gitea so every Vibn
account gets its own isolated tenant boundary, and exposes that
boundary to AI agents (Cursor, Claude Code, scripts) through
per-workspace bearer tokens.

Schema (additive, idempotent — run /api/admin/migrate once after deploy)
  - vibn_workspaces: slug, name, owner, coolify_project_uuid,
    coolify_team_id (reserved for when Coolify ships POST /teams),
    gitea_org, provision_status
  - vibn_workspace_members: room for multi-user workspaces later
  - vibn_workspace_api_keys: sha256-hashed bearer tokens
  - fs_projects.vibn_workspace_id: nullable FK linking projects
    to their workspace

Provisioning
  - On first sign-in, ensureWorkspaceForUser() inserts the row
    (no network calls — keeps signin fast).
  - On first project create, ensureWorkspaceProvisioned() lazily
    creates a Coolify Project (vibn-ws-{slug}) and a Gitea org
    (vibn-{slug}). Failures are recorded on the row, not thrown,
    and POST /api/workspaces/{slug}/provision retries.

Auth surface
  - lib/auth/workspace-auth.ts: requireWorkspacePrincipal() accepts
    either a NextAuth session or "Authorization: Bearer vibn_sk_...".
    The bearer key is hard-pinned to one workspace — it cannot
    reach any other tenant.
  - mintWorkspaceApiKey / listWorkspaceApiKeys / revokeWorkspaceApiKey

Routes
  - GET    /api/workspaces                         list
  - GET    /api/workspaces/[slug]                  details
  - POST   /api/workspaces/[slug]/provision        retry provisioning
  - GET    /api/workspaces/[slug]/keys             list keys
  - POST   /api/workspaces/[slug]/keys             mint key (token shown once)
  - DELETE /api/workspaces/[slug]/keys/[keyId]     revoke

UI
  - components/workspace/WorkspaceKeysPanel.tsx: identity card,
    keys CRUD with one-time secret reveal, and a "Connect Cursor"
    block with copy/download for:
      .cursor/rules/vibn-workspace.mdc — rule telling the agent
        about the API + workspace IDs + house rules
      ~/.cursor/mcp.json — MCP server registration with key
        embedded (server URL is /api/mcp; HTTP MCP route lands next)
      .env.local — VIBN_API_KEY + smoke-test curl
  - Slotted into existing /[workspace]/settings between Workspace
    and Notifications cards (no other layout changes).

projects/create
  - Resolves the user's workspace (creating + provisioning lazily).
  - Repos go under workspace.gitea_org (falls back to GITEA_ADMIN_USER
    for backwards compat).
  - Coolify services are created inside workspace.coolify_project_uuid
    (renamed {slug}-{appName} to stay unique within the namespace) —
    no more per-Vibn-project Coolify Project sprawl.
  - Stamps vibn_workspace_id on fs_projects.

lib/gitea
  - createOrg, getOrg, addOrgOwner, getUser
  - createRepo now routes /orgs/{owner}/repos when owner != admin

Also includes prior-turn auth hardening that was already in
authOptions.ts (CredentialsProvider for dev-local, isLocalNextAuth
cookie config) bundled in to keep the auth layer in one consistent
state.

.env.example
  - Documents GITEA_API_URL / GITEA_API_TOKEN / GITEA_ADMIN_USER /
    GITEA_WEBHOOK_SECRET and COOLIFY_URL / COOLIFY_API_TOKEN /
    COOLIFY_SERVER_UUID, with the canonical hostnames
    (git.vibnai.com, coolify.vibnai.com).

Post-deploy
  - Run once: curl -X POST https://vibnai.com/api/admin/migrate \\
      -H "x-admin-secret: \$ADMIN_MIGRATE_SECRET"
  - Existing users get a workspace row on next sign-in.
  - Existing fs_projects keep working (legacy gitea owner + their
    own per-project Coolify Projects); new projects use the
    workspace-scoped path.

Not in this commit (follow-ups)
  - Wiring requireWorkspacePrincipal into the rest of /api/projects/*
    so API keys can drive existing routes
  - HTTP MCP server at /api/mcp (the mcp.json snippet already
    points at the right URL — no client re-setup when it lands)
  - Backfill script to assign legacy fs_projects to a workspace

Made-with: Cursor
2026-04-20 17:17:12 -07:00
26429f3517 feat(agent): event timeline API, SSE stream, Coolify DDL, env template
- Add agent_session_events table + GET/POST events + SSE stream routes
- Build Agent tab: hydrate from events + EventSource while running
- entrypoint: create agent_sessions + agent_session_events on container start
- .env.example for AGENT_RUNNER_URL, AGENT_RUNNER_SECRET, DATABASE_URL

Made-with: Cursor
2026-04-01 11:48:55 -07:00
f47205c473 rename: replace all user-facing 'Atlas' references with 'Vibn'
Updated UI text in: project-shell (tab label), AtlasChat (sender name),
FreshIdeaMain, TypeSelector, MigrateSetup, ChatImportSetup, FreshIdeaSetup,
CodeImportSetup, prd/page, build/page, projects/page, deployment/page,
activity/page, layout (page title/description), atlas-chat API route.
Code identifiers (AtlasChat component name, file names) unchanged.

Made-with: Cursor
2026-03-17 16:25:41 -07:00
46efc41812 feat: add Architecture tab to PRD page and inject arch into COO context
- PRD page now has a tabbed view: PRD | Architecture
  Architecture tab renders apps, packages, infrastructure, integrations,
  and risk notes as structured cards. Only shown when arch doc exists.
- Advisor route now includes the architecture summary and key fields
  in the COO's knowledge context so the orchestrator knows what's
  been planned technically

Made-with: Cursor
2026-03-10 17:03:43 -07:00
cff5cd6014 fix: pass full PRD to COO without truncation
Made-with: Cursor
2026-03-10 16:36:47 -07:00
8f95270b12 feat: Assist COO routes through Orchestrator on agent runner
The advisor route now proxies to /orchestrator/chat on agents.vibnai.com
instead of calling Gemini directly. The Orchestrator (Claude Sonnet 4.6)
has full tool access — Gitea, Coolify, web search, memory, agent spawning.

- Build project knowledge_context from DB (name, vision, repo, PRD,
  phases, apps, recent sessions) and inject as COO persona + data
- Convert frontend history format (model→assistant) for the orchestrator
- Return orchestrator reply as streaming text response
- Session scoped per project for in-memory context persistence

Made-with: Cursor
2026-03-09 22:32:01 -07:00
ff0e1592fa feat(advisor): load real PRD, phases, sessions, apps into COO system prompt
Made-with: Cursor
2026-03-09 22:14:35 -07:00
528d6bb1e3 fix: remove colon from Coolify project description — fails Coolify validation
Made-with: Cursor
2026-03-09 18:20:33 -07:00
6901a97db3 feat(migrate): wire GitHub PAT through to agent runner mirror call
MigrateSetup now sends the PAT field to the API; create route
forwards it as github_token so the agent runner can clone private repos.

Made-with: Cursor
2026-03-09 18:05:12 -07:00
0e204ced89 feat: store coolifyProjectUuid on project creation for Infrastructure panel
Made-with: Cursor
2026-03-09 17:40:21 -07:00
7979fd0518 fix: detect apps in any repo structure, not just turborepo or flagged imports
Made-with: Cursor
2026-03-09 17:23:38 -07:00
22f4c4f1c3 fix: preview URL resolved from Gitea repo via Coolify git_repository match
Made-with: Cursor
2026-03-09 17:14:55 -07:00
5778abe6c3 feat: add live app preview panel with iframe, URL bar, and reload
Made-with: Cursor
2026-03-09 17:07:33 -07:00
853e41705f feat: split top navbar to align with chat/content panels, fix Gemini API key
- Top bar left section (320px) = logo + project name, aligns with chat panel
- Top bar right section = Build|Market|Assist pills + tool icons (Preview, Tasks, Code, Design, Backend) + avatar
- Read GOOGLE_API_KEY inside POST handler (not top-level) to ensure env is resolved at request time

Made-with: Cursor
2026-03-09 16:17:31 -07:00
01848ba682 feat: add persistent COO/Assist chat as left-side primary AI interface
- New CooChat component: streaming Gemini-backed advisor chat, message
  bubbles, typing cursor animation, Shift+Enter for newlines
- New /api/projects/[projectId]/advisor streaming endpoint: builds a
  COO system prompt from project context (name, description, vision,
  repo), proxies Gemini SSE stream back to the client
- Restructured BuildHubInner layout:
    Left (340px): CooChat — persistent across all Build sections
    Inner nav (200px): Build pills + contextual items (apps, tree, surfaces)
    Main area: File viewer for Code, Layouts content, Infra content
- AgentMode removed from main view — execution surfaces via COO delegation

Made-with: Cursor
2026-03-09 15:34:41 -07:00
01c2d33208 fix: strip backticks from CODEBASE_MAP.md path parsing
Paths wrapped in backticks like `app/` were being captured with
the backtick character, producing invalid app names and paths.

Made-with: Cursor
2026-03-09 14:21:25 -07:00
65adcd4897 feat: detect apps for imported non-turborepo projects
- Fall back to CODEBASE_MAP.md parsing when no apps/ dir exists
- Further fallback: scan top-level dirs for deployable app signals
  (package.json, Dockerfile, requirements.txt, next.config.*, etc.)
- Skips docs, scripts, keys, and other non-app directories
- Returns isImport flag to frontend for context

Made-with: Cursor
2026-03-09 11:52:10 -07:00
9c277fd8e3 feat: add GitHub import flow, project delete fix, and analyze API
- Mirror GitHub repos to Gitea as-is on import (skip scaffold)
- Auto-trigger ImportAnalyzer agent after successful mirror
- Add POST/GET /api/projects/[projectId]/analyze route
- Fix project delete button visibility (was permanently opacity:0)
- Store isImport, importAnalysisStatus, importAnalysisJobId on projects

Made-with: Cursor
2026-03-09 11:30:51 -07:00
fc59333383 feat: auto-approve UI + session status approved
- sessions POST: look up coolifyServiceUuid, pass autoApprove:true to runner
- sessions PATCH: approved added to terminal statuses (sets completed_at)
- build/page.tsx: approved status, STATUS_COLORS/LABELS for "Shipped",
  auto-committed UI in changed files panel, bottom bar for approved state
- Architecture doc: fully updated with current state

Made-with: Cursor
2026-03-07 13:17:33 -08:00
7f61295637 fix: remove ::uuid casts on project_id/p.id in all agent session routes
Made-with: Cursor
2026-03-07 12:44:45 -08:00
8c19dc1802 feat: agent session retry + follow-up UX
- retry/route.ts: reset failed/stopped session and re-fire agent runner
  with optional continueTask follow-up text
- build/page.tsx: Retry button and Follow up input appear on failed/stopped
  sessions so users can continue without losing context or creating a
  duplicate session; task input hint clarifies each Run = new session

Made-with: Cursor
2026-03-07 12:25:58 -08:00
28b48b74af fix: surface agent_sessions 500 and add db migration
- sessions/route.ts: replace inline CREATE TABLE DDL with a lightweight
  existence check; add `details` to all 500 responses; fix type-unsafe
  `p.id = $1::uuid` comparisons to `p.id::text = $1` to avoid the
  Postgres `text = uuid` operator error
- app/api/admin/migrate: one-shot idempotent migration endpoint secured
  with ADMIN_MIGRATE_SECRET, creates fs_* tables + agent_sessions
- scripts/migrate-fs-tables.sql: formal schema for all fs_* tables

Made-with: Cursor
2026-03-07 12:16:16 -08:00
f7d38317b2 fix: add ::uuid casts to all agent_sessions queries
PostgreSQL can't implicitly coerce text params to UUID columns.
Add explicit ::uuid casts on id and project_id in all agent session
routes (list, get, patch, stop, approve).

Made-with: Cursor
2026-03-07 11:49:40 -08:00
18f61fe95c approve & commit flow + adaptive polling in Agent mode
- Wire Approve & commit button: shows commit message input, calls
  POST /api/.../sessions/[id]/approve which asks agent runner to
  git commit + push, then marks session as approved in DB
- Adaptive polling: 500ms while session running, 5s when idle —
  output feels near-real-time without hammering the API
- Auto-refresh session list when a session completes
- Open in Theia links to theia.vibnai.com (escape hatch for manual edits)

Made-with: Cursor
2026-03-07 11:36:55 -08:00
61a43ad9b4 pass giteaRepo to agent runner; add runner secret auth on PATCH
- Sessions route now reads giteaRepo from project.data and forwards it
  to /agent/execute so the runner can clone/update the correct repo
- PATCH route now validates x-agent-runner-secret header to prevent
  unauthorized session output injection

Made-with: Cursor
2026-03-06 18:01:33 -08:00
ad3abd427b feat: agent execution scaffold — sessions DB, API, and Browse/Agent/Terminal UI
Session model:
- agent_sessions table (auto-created on first use): id, project_id,
  app_name, app_path, task, status, output (JSONB log), changed_files,
  error, timestamps
- POST /agent/sessions — create session, fires off to agent-runner
  (gracefully degrades when runner not yet wired)
- GET  /agent/sessions — list sessions newest first
- GET  /agent/sessions/[id] — full session state for polling
- PATCH /agent/sessions/[id] — internal: agent-runner appends output lines
- POST /agent/sessions/[id]/stop — stop running session

Build > Code section now has three mode tabs:
- Browse — existing file tree + code viewer
- Agent — task input, session list sidebar, live output stream,
           changed files panel, Approve & commit / Open in Theia actions,
           2s polling (Phase 3 will replace with WebSocket)
- Terminal — xterm.js placeholder (Phase 4)

Architecture documented in AGENT_EXECUTION_ARCHITECTURE.md

Made-with: Cursor
2026-03-06 17:56:10 -08:00
e08fcf674b feat: VIBN-branded file browser on Build tab + sidebar status dot
- Build page: full file tree (lazy-load dirs) + code preview panel
  with line numbers and token-level syntax colouring (VS Code dark theme)
- New API route /api/projects/[id]/file proxies Gitea contents API
  returning directory listings or decoded file content
- Sidebar Apps section now links to /build instead of raw Gitea URL
- Status indicator replaced with a proper coloured dot (amber/blue/green)
  alongside the status label text

Made-with: Cursor
2026-03-06 13:37:38 -08:00
ab100f2e76 feat: implement 4 project type flows with unique AI experiences
- New multi-step CreateProjectFlow replaces 2-step modal with TypeSelector
  and 4 setup components (Fresh Idea, Chat Import, Code Import, Migrate)
- overview/page.tsx routes to unique main component per creationMode
- FreshIdeaMain: wraps AtlasChat with post-discovery decision banner
  (Generate PRD vs Plan MVP Test)
- ChatImportMain: 3-stage flow (intake → extracting → review) with
  editable insight buckets (decisions, ideas, questions, architecture, users)
- CodeImportMain: 4-stage flow (input → cloning → mapping → surfaces)
  with architecture map and surface selection
- MigrateMain: 5-stage flow with audit, review, planning, and migration
  plan doc with checkbox-tracked tasks and non-destructive warning banner
- New API routes: analyze-chats, analyze-repo, analysis-status,
  generate-migration-plan (all using Gemini)
- ProjectShell: accepts creationMode prop, filters/renames tabs per type
  (code-import hides PRD, migration hides PRD/Grow/Insights, renames Atlas tab)
- Right panel adapts content based on creationMode

Made-with: Cursor
2026-03-06 12:48:28 -08:00
24812df89b design-surfaces: explicit ::text cast on every query param
Add ::text cast to all $1/$2 parameters so PostgreSQL never needs
to infer types. Split SELECT and UPDATE into separate try/catch blocks
with distinct error labels so logs show exactly which query fails.

Made-with: Cursor
2026-03-06 11:29:57 -08:00