{
  "id": "nextjs-supabase-saas",
  "type": "loadout",
  "title": "Next.js + Supabase SaaS",
  "summary": "Rules, an RLS audit skill and a read-only Supabase MCP config for vibe-coding a Next.js App Router SaaS without leaking your database.",
  "description": "The stack most vibe-coded SaaS apps end up on, with the guard rails agents usually forget.\nYour agent learns where server code ends and client code begins, how to use Supabase auth\nwith cookies, and that every table needs row level security before it holds real data.\n",
  "highlights": [
    "Clear server/client boundary rules for the App Router",
    "Migrations-only schema changes, with RLS enabled on every new table",
    "A skill that audits every table's RLS policies and reports gaps in plain English",
    "Read-only Supabase MCP config so the agent can inspect, not drop, your database"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "categories": [
    "web-app"
  ],
  "stacks": [
    "nextjs",
    "react",
    "typescript",
    "supabase",
    "postgres"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Project rules for any agent that reads AGENTS.md.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/AGENTS.md",
      "bytes": 3421,
      "lang": "markdown",
      "content": "# AGENTS.md: Next.js + Supabase SaaS\n\nYou are working on a Next.js (App Router) SaaS backed by Supabase (Postgres, Auth, Storage).\nTreat the database as production unless told otherwise.\n\n## Stack and layout\n- Next.js App Router in `app/`. TypeScript strict mode. No Pages Router code.\n- Supabase clients live in `lib/supabase/`:\n  - `server.ts` creates a server client with `@supabase/ssr` and the Next.js `cookies()` store.\n  - `client.ts` creates a browser client. Only use it inside `\"use client\"` components.\n  - `middleware.ts` (or `proxy.ts` on newer Next.js) refreshes the auth session cookie on each request.\n- Schema lives in `supabase/migrations/`. Generated types live in `lib/database.types.ts`.\n\n## Server vs client\n- Components are Server Components by default. Add `\"use client\"` only for state, effects or browser APIs.\n- Fetch data in Server Components or Route Handlers, not in `useEffect`, unless it is genuinely live data.\n- Mutations go through Server Actions or Route Handlers. Validate every input with `zod` on the server,\n  even if the form validated it already.\n- Never import `lib/supabase/server.ts` or anything using secret env vars from a client component.\n\n## Auth\n- Get the user on the server with `supabase.auth.getUser()` (it verifies the JWT with Supabase).\n  Do not trust `getSession()` on the server for authorisation decisions.\n- Protect pages by checking the user in the layout or page, and redirect if missing. Middleware alone is not enough.\n- Authorisation belongs in RLS policies. App code checks are a convenience, not the security boundary.\n\n## Keys and environment\n- `NEXT_PUBLIC_SUPABASE_URL` and the publishable (anon) key are safe in the browser *only because RLS is on*.\n- The secret / `service_role` key bypasses RLS. It must only be used in server-only code for admin jobs,\n  never prefixed `NEXT_PUBLIC_`, never logged, never committed.\n- Keep `.env.local` out of git. Add any new variable to `.env.example` with a dummy value.\n\n## Database changes\n- Every schema change is a new migration: `supabase migration new <name>`. Never edit an applied migration.\n- Every new table in an exposed schema gets `alter table ... enable row level security;` in the same migration,\n  plus explicit policies for each operation it needs (select, insert, update, delete).\n- Prefer `(select auth.uid())` in policies over bare `auth.uid()` so Postgres can cache it per statement.\n- Add indexes on columns used in policies (for example `user_id`, `org_id`).\n- After a schema change, regenerate types: `supabase gen types typescript --local > lib/database.types.ts`.\n\n## Payments (if present)\n- Stripe webhooks are verified with the signing secret before any database write.\n- Entitlements (plan, seats) are written by the webhook handler, never by the client.\n\n## Before you say \"done\"\n1. `npm run lint` and `npm run build` pass.\n2. No secret key reachable from client bundles (`grep -r SERVICE_ROLE app components` is empty).\n3. Any new table has RLS enabled and at least one policy. Run the `supabase-rls-audit` skill if unsure.\n4. Summarise what changed, any migration added, and anything the human must do (env vars, dashboard settings).\n\n## Do not\n- Do not disable RLS to \"make it work\". Fix the policy instead.\n- Do not run destructive SQL (`drop`, `truncate`, `delete` without `where`) against a remote project.\n- Do not add a new auth library. Supabase Auth is the auth system.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/CLAUDE.md",
      "bytes": 509,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `supabase-rls-audit`. Use them when their description matches the task.\n- MCP servers for this project are in `.mcp.json`. If a server is not connected, tell the human rather than working around it.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Next.js + Supabase SaaS\" v1.0.0 — https://loadout.jnw-it.com/loadouts/nextjs-supabase-saas/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/GEMINI.md",
      "bytes": 383,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `supabase-rls-audit`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Next.js + Supabase SaaS\" v1.0.0 — https://loadout.jnw-it.com/loadouts/nextjs-supabase-saas/ -->\n"
    },
    {
      "path": ".cursor/rules/supabase-migrations.mdc",
      "role": "rules",
      "description": "Scoped Cursor rule that applies when editing SQL migrations.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": ".cursor/rules/supabase-migrations.mdc",
        "gemini-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/.cursor/rules/supabase-migrations.mdc",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/.cursor/rules/supabase-migrations.mdc",
      "bytes": 1768,
      "lang": "markdown",
      "content": "---\ndescription: Rules for writing Supabase SQL migrations safely\nglobs: supabase/migrations/**/*.sql\nalwaysApply: false\n---\n\n# Supabase migrations\n\n- One concern per migration file. Name it for what it does: `add_projects_table`, not `update`.\n- Never edit a migration that has been applied anywhere. Write a new one.\n- Every `create table` in `public` (or any exposed schema) is followed in the same file by:\n  - `alter table <name> enable row level security;`\n  - explicit `create policy` statements for each operation the app needs.\n- Write policies with a `to` role (`authenticated` or `anon`) and both `using` and, for writes, `with check`.\n- Use `(select auth.uid())` inside policies, not bare `auth.uid()`.\n- Index foreign keys and every column referenced by a policy.\n- `security definer` functions must set `search_path = ''` and fully qualify table names.\n- Destructive changes (`drop column`, `drop table`, type changes) need a comment explaining the data impact,\n  and should be split into expand → migrate data → contract steps when the table has real data.\n\nExample:\n\n```sql\ncreate table public.projects (\n  id uuid primary key default gen_random_uuid(),\n  owner_id uuid not null references auth.users (id) on delete cascade,\n  name text not null check (char_length(name) between 1 and 120),\n  created_at timestamptz not null default now()\n);\n\nalter table public.projects enable row level security;\n\ncreate index projects_owner_id_idx on public.projects (owner_id);\n\ncreate policy \"Owners can read their projects\"\n  on public.projects for select to authenticated\n  using ((select auth.uid()) = owner_id);\n\ncreate policy \"Owners can create projects\"\n  on public.projects for insert to authenticated\n  with check ((select auth.uid()) = owner_id);\n```\n"
    },
    {
      "path": "skills/supabase-rls-audit/SKILL.md",
      "role": "skill",
      "description": "Audits row level security across every table and reports gaps.",
      "skill": "supabase-rls-audit",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/supabase-rls-audit/SKILL.md",
        "codex": ".agents/skills/supabase-rls-audit/SKILL.md",
        "cursor": ".agents/skills/supabase-rls-audit/SKILL.md",
        "gemini-cli": ".agents/skills/supabase-rls-audit/SKILL.md",
        "copilot": ".agents/skills/supabase-rls-audit/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/skills/supabase-rls-audit/SKILL.md",
      "bytes": 3131,
      "lang": "markdown",
      "content": "---\nname: supabase-rls-audit\ndescription: Audit Supabase row level security. Use when asked to check database security, before launch, after adding tables, or when someone worries that users can see each other's data.\n---\n\n# Supabase RLS audit\n\nGoal: find every way a signed-in or anonymous user could read or change rows they should not,\nand report it in plain English with a fix for each.\n\n## 1. Collect the facts\nUse whichever source is available, in this order:\n1. The Supabase MCP server (read-only) — run the SQL below with its query tool.\n2. A local database: `supabase db dump --local --schema public` or `psql` against the local URL.\n3. The migration files in `supabase/migrations/` (read them in order; later files win).\n\n```sql\n-- Tables in exposed schemas and whether RLS is on\nselect n.nspname as schema, c.relname as table, c.relrowsecurity as rls_enabled, c.relforcerowsecurity as rls_forced\nfrom pg_class c join pg_namespace n on n.oid = c.relnamespace\nwhere c.relkind = 'r' and n.nspname in ('public')\norder by 1, 2;\n\n-- Every policy\nselect schemaname, tablename, policyname, cmd, roles, qual as using_expr, with_check\nfrom pg_policies where schemaname in ('public')\norder by tablename, cmd;\n\n-- Views (they bypass RLS unless created with security_invoker)\nselect table_schema, table_name from information_schema.views where table_schema = 'public';\n\n-- Security definer functions callable through the API\nselect p.proname, p.prosecdef, p.proconfig\nfrom pg_proc p join pg_namespace n on n.oid = p.pronamespace\nwhere n.nspname = 'public' and p.prosecdef;\n```\n\n## 2. Check each table against these rules\nFlag as **Critical**:\n- RLS disabled on any table in `public`.\n- A policy for `anon` or `public` roles that is `using (true)` on a table holding user data.\n- An `update` policy without `with check`, letting users move rows to another owner.\n- A view in `public` without `security_invoker = true` that selects from a protected table.\n\nFlag as **High**:\n- RLS enabled but no policies (the app is probably using the secret key to get round it — find out where).\n- Policies that trust a column the user can write (for example `role` or `is_admin` on their own profile row).\n- `security definer` functions without `set search_path = ''`.\n- Storage buckets that are public but hold private uploads.\n\nFlag as **Medium**:\n- Bare `auth.uid()` instead of `(select auth.uid())` (performance, not security).\n- Policy columns without indexes.\n\n## 3. Check the app code\n- Search for the secret / service role key: `grep -rn \"SERVICE_ROLE\\|sb_secret_\" --include=*.ts --include=*.tsx .`\n  Any hit outside server-only files is **Critical**.\n- Check that no `NEXT_PUBLIC_` variable holds a secret.\n\n## 4. Report\nWrite a short report:\n\n```\nRLS audit — <date>\nCritical: <n>  High: <n>  Medium: <n>\n\n[Critical] public.invoices — RLS is disabled. Anyone with the publishable key can read every invoice.\nFix: new migration with `alter table public.invoices enable row level security;` and an owner-only select policy.\n```\n\nThen offer to write the fix as a **new migration**. Do not apply changes to a remote database yourself.\n"
    },
    {
      "path": "mcp.json",
      "role": "mcp",
      "description": "Supabase hosted MCP server in read-only mode, scoped to one project.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": ".mcp.json",
        "codex": null,
        "cursor": ".cursor/mcp.json",
        "gemini-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/latest/mcp.json",
      "bytes": 160,
      "lang": "json",
      "content": "{\n  \"mcpServers\": {\n    \"supabase\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.supabase.com/mcp?project_ref=YOUR_PROJECT_REF&read_only=true\"\n    }\n  }\n}\n"
    }
  ],
  "requires": {
    "tools": [
      "node",
      "supabase-cli"
    ],
    "env": [
      "NEXT_PUBLIC_SUPABASE_URL",
      "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY"
    ],
    "mcp": [
      "supabase"
    ]
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [],
    "notes": "The MCP config connects to Supabase's hosted MCP server. It is set to read-only and needs your project ref."
  },
  "featured": true,
  "deprecated": false,
  "last_checked": "2026-10-15",
  "published": "2026-10-15",
  "updated": "2026-10-15",
  "compatibility": {
    "claude-code": "native",
    "codex": "adapter",
    "cursor": "native",
    "gemini-cli": "adapter",
    "copilot": "adapter"
  },
  "counts": {
    "rules": 2,
    "skills": 1,
    "mcp": 1
  },
  "url": "https://loadout.jnw-it.com/loadouts/nextjs-supabase-saas/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/nextjs-supabase-saas.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-16T22:54:03Z",
    "commit": "ad36e852567aa3c87821d2a9e2fe4052fd983374",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 10,
      "fail": 0,
      "skip": 0,
      "not_applicable": 0
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 6 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 6 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 5 agents; layouts re-discovered",
        "details": [
          "Claude Code: 4 files, 1 skill in .claude/skills/, MCP at .mcp.json",
          "Codex CLI: 2 files, 1 skill in .agents/skills/, MCP manual step",
          "Cursor: 4 files, 1 skill in .agents/skills/, 1 rule, MCP at .cursor/mcp.json",
          "Gemini CLI: 3 files, 1 skill in .agents/skills/, MCP manual step",
          "GitHub Copilot: 2 files, 1 skill in .agents/skills/, MCP manual step"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "pass",
        "summary": "1 server valid: supabase",
        "details": [
          "mcp.json → supabase: user must fill in YOUR_PROJECT_REF",
          "mcp.json → supabase: mcp.supabase.com is reachable over HTTPS",
          "Converts to Codex config.toml, Gemini CLI settings.json and VS Code mcp.json"
        ]
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network yes (listing says yes)",
          "info mcp-server in mcp.json: supabase: url https://mcp.supabase.com/mcp?project_ref=YOUR_PROJECT_REF&read_only=true"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "2 references between files resolve for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "6 files, 9.2 KB total; largest AGENTS.md 3.3 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "Next.js App Router + @supabase/ssr app in the loadout's layout builds, and the loadout's SQL migration and RLS audit queries run on real Postgres with users isolated",
        "details": [
          "Applied 8 loadout files at every agent's paths",
          "Example from .cursor/rules/supabase-migrations.mdc:23 (sql) → supabase/migrations/20261001000000_add_projects_table.sql",
          "Example from skills/supabase-rls-audit/SKILL.md:17 (sql) → audit.sql",
          "✓ install: npm ci --no-audit --no-fund",
          "✓ SQL on Postgres (PGlite): node check-sql.mjs",
          "✓ next build: npm run build",
          "Covers: lib/supabase/server.ts, client.ts and proxy.ts as AGENTS.md lays out; getUser() on the server; zod-validated Server Action",
          "Covers: The Cursor rule's example migration applies on Postgres; the audit skill's four queries run and report RLS on",
          "Covers: RLS behaviour: a user sees only their rows, cannot insert as another user, anon sees nothing",
          "Covers: next build (type-check included); no Supabase project or keys needed"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/supabase-rls-audit/SKILL.md",
          "target": ".claude/skills/supabase-rls-audit/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
          "role": "skill"
        },
        {
          "path": "mcp.json",
          "target": ".mcp.json",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json",
          "role": "mcp"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/supabase-rls-audit/SKILL.md",
          "target": ".agents/skills/supabase-rls-audit/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [
        {
          "path": "mcp.json",
          "note": "Codex reads MCP servers from ~/.codex/config.toml (TOML, not JSON). Add each server with `codex mcp add` or copy the URL into an [mcp_servers.<name>] table.",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json"
        }
      ],
      "zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/supabase-migrations.mdc",
          "target": ".cursor/rules/supabase-migrations.mdc",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/.cursor/rules/supabase-migrations.mdc",
          "role": "rules"
        },
        {
          "path": "skills/supabase-rls-audit/SKILL.md",
          "target": ".agents/skills/supabase-rls-audit/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
          "role": "skill"
        },
        {
          "path": "mcp.json",
          "target": ".cursor/mcp.json",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json",
          "role": "mcp"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/supabase-rls-audit/SKILL.md",
          "target": ".agents/skills/supabase-rls-audit/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [
        {
          "path": "mcp.json",
          "note": "Gemini CLI reads MCP servers from the mcpServers block of .gemini/settings.json. Merge the servers in by hand, and use httpUrl instead of url for HTTP servers.",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json"
        }
      ],
      "zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/gemini-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/supabase-rls-audit/SKILL.md",
          "target": ".agents/skills/supabase-rls-audit/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [
        {
          "path": "mcp.json",
          "note": "In VS Code, MCP servers go in .vscode/mcp.json under a \"servers\" key (not \"mcpServers\"). Copilot CLI uses ~/.copilot/mcp-config.json.",
          "raw_url": "https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json"
        }
      ],
      "zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/nextjs-supabase-saas/source.zip"
}