---
name: setup-supabase
description: Creates a Supabase project for the current Humand sandbox and wires it up securely (proxy-only, RLS by default, typed). Use when the user says "conectar Supabase", "agregar base de datos", "setup Supabase", or needs persistence in a sandbox.
metadata:
  category: Setup & Environment
---

# Skill: Setup Supabase

Creates a Supabase project via MCP, scaffolds env + migrations, activates RLS, generates TypeScript types, and delegates proxy creation to `/connect-service`.

**Security model:** proxy-only. The serverless functions hold the `service_role` key. The frontend has **no Supabase key at all** — it only talks to `/api/*`. `service_role` is scoped to a single project, so a bug in one sandbox cannot touch other projects.

## When to use

- User wants to add a real database to a sandbox project
- User says "conectar Supabase", "agregar base de datos", "necesito persistencia"
- Invoked manually after `/bootstrap-react-project` — NOT automatic

## Prerequisites

- Project must exist (created via `/bootstrap-react-project`)
- Supabase MCP server must be active (see step 1)
- User is logged into the Humand Supabase org

## Steps

### 1. Validate Supabase MCP is active

Try calling `mcp__claude_ai_Supabase__list_organizations`. If the tool is not available or fails with "not connected":

> Necesito el MCP de Supabase para crear el proyecto. Podés instalarlo desde Claude Desktop → Settings → Connectors → Supabase, o desde https://supabase.com/dashboard/account/integrations. Cuando esté listo, avisame y sigo.

Stop. Wait for the user to confirm before retrying.

### 2. Validate org is Humand

Call `list_organizations`. Look for the org with slug `khdchcnpcrjixefcmfwy` (name: "Humand"). If it's not in the list, stop and tell the user:

> El MCP de Supabase no tiene acceso a la org de Humand. Revisá que estés logueado con tu cuenta `@humand.co` y que el MCP apunte a esa org.

Do NOT create projects in a non-Humand org under any circumstance.

### 3. Compute unique project name (delegate to Haiku)

Name format: `humand-sandbox-<slug>-<hash>` where slug = last segment of the project directory (lowercased, non-alphanumeric → `-`) and hash = 4 random hex chars.

```
Agent({
  model: "haiku",
  prompt: `
    In {projectPath}:
    1. Compute slug = basename(pwd), lowercased, non-alphanumeric replaced with "-"
    2. Compute hash via: bun -e "console.log(require('crypto').randomBytes(2).toString('hex'))"
    3. Print the final name: humand-sandbox-<slug>-<hash>
    Output only the name on stdout, nothing else.
  `
})
```

Show the returned name and ask the user to confirm before creating.

### 4. Create the Supabase project

Use `get_cost` + `confirm_cost` if the MCP requires it, then `create_project` with:
- `name`: the unique name from step 3
- `organization_id`: `khdchcnpcrjixefcmfwy`
- `region`: default (or ask the user)

Capture from the response:
- `id` (project id — UUID)
- `ref` (project ref — short slug in the dashboard URL)
- `url` (project URL, `https://<ref>.supabase.co`)

Wait for the project to finish provisioning. If the MCP exposes a status check, poll; otherwise retry `get_project` a few times with a short delay.

### 5. Retrieve the service_role key

Use the appropriate MCP call to fetch the service_role key for the project. **This key is a secret** — never log it, never echo it to chat output, and never write it outside `.env.local`.

> ⚠️ If the MCP cannot return the service_role key programmatically, ask the user to copy it from the dashboard:
> https://supabase.com/dashboard/project/<ref>/settings/api-keys
> Paste it, and continue. Do NOT proceed without it.

### 6. Run the scaffold script (delegate to Haiku)

The script writes `.env.local`, updates `.env.example` (non-secret values only), creates `supabase/migrations/`, and installs `@supabase/supabase-js`. Stdout is a JSON summary.

**Do NOT pass the service_role key in a plain prompt to the sub-agent** — it's a secret. Instead, export it as an env var and pass only the non-sensitive args:

```
Agent({
  model: "haiku",
  prompt: `
    In {projectPath}, read SUPABASE_SERVICE_ROLE_KEY from the environment and run:

    bun <plugin-dir>/skills/setup-supabase/scripts/scaffold.ts \\
      --project-dir "$(pwd)" \\
      --project-id "<id>" \\
      --project-ref "<ref>" \\
      --project-url "<url>" \\
      --service-role-key "$SUPABASE_SERVICE_ROLE_KEY"

    Report the JSON summary from stdout. Do NOT echo the key.
  `
})
```

### 7. Enable RLS by default

At this point the database is empty. Policy: **every new table must have RLS (Row Level Security) enabled before it's used**. Tell the user in plain language — don't assume they know what RLS is:

> Toda tabla que creemos va a tener "Row Level Security" activado. En criollo: Supabase no deja que nadie lea o escriba la tabla a menos que vos definas reglas explícitas de quién puede. Es lo que evita que, si un día exponemos algo sin querer, cualquiera pueda leer toda la base.
>
> Lo vas a ver como una línea extra en cada migración. No tenés que hacer nada — lo manejo yo.

No migration to run yet — this is a standing rule applied later.

### 8. Generate TypeScript types

Call `mcp__claude_ai_Supabase__generate_typescript_types` with the project id (this MCP call must happen in the main conversation, not a sub-agent). Then delegate the file write to Haiku:

```
Agent({
  model: "haiku",
  prompt: `
    In {projectPath}, write the following content to src/types/supabase.ts
    (create the folder if missing):

    <<<TYPES_CONTENT>>>
  `
})
```

Currently the DB is empty so the types file will be mostly empty — that's expected. Re-run after migrations to keep it in sync.

### 9. Delegate proxy creation to /connect-service

Invoke the `connect-service` skill. Pass context:

- Service name: `supabase`
- Operations: ask the user (initially none if the DB is empty — they can add later)
- Env vars already configured: `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`

The proxy handler in `api/supabase/[...path].ts` must:
- Create the Supabase client with `createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY)`
- Keep the `ROUTES` table scoped to the operations requested
- Import types from `src/types/supabase.ts`

### 10. Run advisors and autofix

Call `get_advisors` with type `security` and `performance`. For each finding:

| Advisor | Auto-fix |
|---|---|
| `rls_disabled_in_public` | Emit migration: `ALTER TABLE <t> ENABLE ROW LEVEL SECURITY;` + basic authenticated-read policy |
| `policy_exists_rls_disabled` | Enable RLS on the affected table |
| `function_search_path_mutable` | Emit migration: `ALTER FUNCTION <f> SET search_path = '';` |
| `unindexed_foreign_keys` | Emit migration: `CREATE INDEX ON <table>(<fk_column>);` |
| `auth_otp_long_expiry` | Flag for user — requires dashboard change |
| `auth_leaked_password_protection` | Flag for user — requires dashboard change |

Apply auto-fixes via `apply_migration`. Save each migration to `supabase/migrations/<timestamp>_<description>.sql`.

Applying each migration is a mechanical MCP call — the **reasoning** (matching advisor → fix, writing the SQL) stays with the main model. Once the SQL is drafted, the actual `apply_migration` MCP calls + writing the `.sql` file to `supabase/migrations/` can be delegated to Haiku.

Report a one-line summary to the user — no jargon:

> ✅ Arreglé N cosas de seguridad automáticamente. Tu base está lista.
> (Si quedaron issues manuales: ⚠️ Quedan X cosas que necesitan tu intervención: …)

### 11. Commit

Use a haiku sub-agent:

```
Agent({
  model: "haiku",
  prompt: `
    In {projectPath}:
    1. Stage all changes: git add .
    2. Commit with message: "Supabase configurado (proyecto <ref>)"
       Co-Authored-By: Claude <noreply@anthropic.com>
  `
})
```

### 12. Final summary

Tell the user:

> Supabase configurado. Esto es lo que tenés:
>
> - **Proyecto:** `<name>` — https://supabase.com/dashboard/project/<ref>
> - **Credenciales:** en `.env.local` (gitignored)
> - **Proxy:** `api/supabase/...` — el frontend nunca toca las keys
> - **Tipos TS:** `src/types/supabase.ts` (se regeneran con cada migración)
> - **Migrations:** `supabase/migrations/` (versionadas en el repo)
>
> Próximo paso: si tenés un `db.json` de mock, corré `/migrate-mock-to-supabase` para migrarlo.

---

## Forbidden actions

- Creating projects in any org other than `khdchcnpcrjixefcmfwy` (Humand).
- Writing `SUPABASE_SERVICE_ROLE_KEY` to any file other than `.env.local`, or logging it to chat output.
- Shipping a Supabase client to the frontend bundle (no `createClient` import in `src/`). The only allowed FE import is **types** from `src/types/supabase.ts`.
- Applying migrations that create tables without RLS enabled.

## Security checklist (verify before marking done)

- [ ] Org id matches `khdchcnpcrjixefcmfwy`
- [ ] `service_role` key is only in `.env.local`
- [ ] `.env.example` has placeholders, no secrets
- [ ] Frontend bundle has no Supabase client (only types)
- [ ] All existing tables have RLS enabled
- [ ] Advisors were run and findings were addressed or surfaced to the user
