Skip to content

feat(integration): Day 5 — Supabase agent tools + sandbox env injection#5

Merged
lhz960904 merged 4 commits into
mainfrom
feat/day5-supabase-agent-tools
May 10, 2026
Merged

feat(integration): Day 5 — Supabase agent tools + sandbox env injection#5
lhz960904 merged 4 commits into
mainfrom
feat/day5-supabase-agent-tools

Conversation

@lhz960904

Copy link
Copy Markdown
Owner

Summary

Day 5 of the BYO publish + DB plan. After Day 4 wired Supabase OAuth, this PR teaches the agent to actually use the connection — auto-provision a project under the user's org, run schema/RLS/seed SQL against it, and have the generated Vite app read the project URL + anon key straight out of import.meta.env.

  • supabase_create_project tool — idempotent provisioning. Calls Management API POST /v1/projects, polls GET /v1/projects/{ref} until ACTIVE_HEALTHY (~30–60s, 90s timeout), persists supabase_project_ref + supabase_url + supabase_anon_key on the conversation row, writes .env.local to the running sandbox. If a project already exists for the conversation, returns the existing one and just refreshes .env.local.
  • supabase_sql tool — runs SQL via POST /v1/projects/{ref}/database/query under the user's OAuth Bearer; no service-role key needed. 100-row truncation on returns.
  • supabase-client.ts — adds createSupabaseProject / getSupabaseProject / getSupabaseProjectKeys / runSupabaseSql / listSupabaseOrganizations + 3 typed errors (SupabaseManagementApiError, SupabaseProjectProvisionTimeoutError, SupabaseProjectInitFailedError). All gated through getValidSupabaseAccessToken(userId).
  • acquireConversationSandbox — re-writes .env.local on every acquire from the conversation row so disconnect/rotate/reconnect propagates automatically. Cold starts (sandbox evicted, restart from snapshots) get .env.local back without an agent turn.
  • file-tracker — ignores .env.local + .env.*.local so platform-managed env doesn't pollute fileSnapshots (and therefore doesn't get re-restored on cold start, which would race the new injection).
  • PromptsPROJECT_CONVENTIONS gains a Supabase chapter: when to call supabase_create_project (persistence/auth/storage/realtime only), import.meta.env.VITE_SUPABASE_* is platform-injected (don't write .env.local yourself), always ENABLE ROW LEVEL SECURITY + auth.uid() policies, service-role key is not exposed.
  • Schemaconversations += supabase_url + supabase_anon_key (nullable text). Applied via db:push.

Architectural notes

  • Why Management API + OAuth Bearer instead of service-role key: the OAuth scope already includes Database(RW), so SQL via /v1/projects/{ref}/database/query is enough. Avoids the security footgun of pulling service-role keys into the platform / sandbox.
  • Why store supabase_url + supabase_anon_key on the conversation row: acquireConversationSandbox runs on every cold start and reload; we don't want a Management API round-trip there. Anon key is public-by-design (RLS is the security boundary), so persisting it next to the project ref is fine.
  • Why ignore .env.local in file-tracker: if it landed in fileSnapshots, cold-start would restore the snapshot version while T7's injection writes the current version — order race + stale env. Cleaner to make the platform the sole writer.

Test plan

  • Backend types still pass (bunx tsc --noEmit ✅, run locally to confirm)
  • Backend tests still pass (bun run test — 36/36 pass locally)
  • Manual e2e: open a fresh conversation, ask the agent to "build a todo app with login, persisted to Supabase"
    • Agent calls supabase_create_project → ~30–60s wait → returns ref + URL
    • Agent calls supabase_sql to CREATE TABLE + ENABLE ROW LEVEL SECURITY + CREATE POLICY ... auth.uid() = user_id
    • Agent kills + restarts dev session; preview shows working signup → CRUD
    • In the user's supabase.com dashboard: project exists, table has the expected rows
  • Idempotency: run supabase_create_project a second time on the same conversation → returns the existing ref, no new project created
  • Disconnect Supabase from Settings → trigger supabase_create_project → tool returns the typed error message asking the user to reconnect (not a cryptic 401)
  • Sandbox cold start: restart backend with a conversation that already has a supabase_project_ref.env.local is re-written before bootstrap fires
  • .env.local does NOT appear in fileSnapshots for any test conversation

Out of scope (planned for later)

  • Day 5.5 (5/9): Database panel UI — full CRUD table editor in workspace right panel
  • Day 6 (5/10): Fullstack deploy with @hono/vercel + build-time env injection (.env.production)
  • Day 7 (5/11–12): E2E across 3 project archetypes + README + demo video

🤖 Generated with Claude Code

lhz960904 and others added 2 commits May 8, 2026 09:49
Agents can now auto-provision a Supabase project under the user's
connected org and run DDL/RLS/seed SQL against it via Management API +
OAuth Bearer (no service-role key needed). The platform mirrors the
active project's URL + anon key into the sandbox's .env.local on every
acquire, so generated apps read VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY
straight from import.meta.env.

- supabase_create_project tool: idempotent provisioning, polls until
  ACTIVE_HEALTHY (~30-60s, 90s timeout), persists ref/url/anon_key on
  the conversation row, writes .env.local to current sandbox
- supabase_sql tool: runs SQL via /v1/projects/{ref}/database/query,
  with 100-row truncation
- supabase-client: createSupabaseProject / getSupabaseProjectKeys /
  runSupabaseSql / listSupabaseOrganizations + 3 typed errors
- conversation-sandbox: re-writes .env.local on every acquire so it
  stays in sync with the conversation row (covers reconnect/rotate/
  disconnect transparently)
- file-tracker: ignores .env.local* so platform-managed env doesn't
  pollute fileSnapshots
- prompts: PROJECT_CONVENTIONS gains a Supabase chapter (when to call
  supabase_create_project, RLS + auth.uid() defaults, never expose
  service-role)
- schema: conversations += supabase_url + supabase_anon_key

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… a skill

Per review feedback: Supabase usage is a per-project concern, not a
universal one — every conversation paying tokens for it (even pure
static-site builds) is wasteful. Move the full guide into a sandbox
skill so the agent only loads it on relevant triggers.

- New skill at sandbox-template/skills/supabase/SKILL.md
  (mirrors hono-fullstack's structure)
  - Workflow leads with "ALWAYS supabase_create_project first
    (idempotent)" — agent doesn't need to track provisioning state
  - Concrete recipes: RLS policies, auth signUp/signIn, file storage
    bucket creation, realtime channel + ALTER PUBLICATION
  - Don'ts cover the platform contract (no .env.local writes, no
    service-role attempts, no skipping RLS)
- PROJECT_CONVENTIONS shrinks the 2-paragraph chapter back to a single
  pointer line: "When the app needs persistent data ... use the
  supabase skill"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lhz960904

Copy link
Copy Markdown
Owner Author

Addressed review point 1 in cc3cb06: moved the Supabase chapter out of PROJECT_CONVENTIONS and into a proper skill at sandbox-template/skills/supabase/SKILL.md.

Why this matters: every conversation was paying prompt tokens for the 2-paragraph Supabase block, even when building static sites that don't touch Supabase. Skills are loaded on-demand by description triggers — pure-static conversations now pay zero.

Skill design highlights:

  • Workflow opens with the rule Haoze called out: "ALWAYS supabase_create_project first, every time you touch data — it's idempotent so this is the cheapest way to guarantee env is wired." Agent doesn't track provisioning state.
  • Concrete recipes for the 80% case (RLS templates, sign-up/in, storage bucket creation, realtime channel + ALTER PUBLICATION).
  • "Don'ts" section codifies the platform contract: don't write .env.local, don't try service-role, don't skip RLS, don't add a server-side SUPABASE_SECRET_KEY client to the generated app's Hono backend.

Still on the table for follow-up (review point 2 was deferred):

  • Move the kill-dev-session responsibility from agent to platform, so the skill drops the manual kill_shell step.
  • Day 6 will mirror .env.local to .env.production at deploy time, completing the symmetric "platform owns env files, agent only sees import.meta.env" architecture.

Note: the skill source ships in sandbox-template/skills/, which gets baked into the E2B image via e2b.Dockerfile's COPY skills /opt/skills. Deploying this change end-to-end requires rebuilding + uploading the sandbox template — same flow as adding hono-fullstack originally.

@lhz960904
lhz960904 force-pushed the feat/day5-supabase-agent-tools branch from 6337682 to cc3cb06 Compare May 9, 2026 06:36
lhz960904 and others added 2 commits May 10, 2026 16:56
…on connection

- Rename supabase-app → frontend-starter; the template is now a neutral
  Vite + React + TanStack starter with no Supabase coupling (drop
  @supabase/supabase-js, supabase.ts, use-auth.ts, auth demo route).
- Persistence is conditional and decided server-side: when the user has
  connected Supabase, the agent run includes supabase_create_project +
  supabase_sql tools and the supabase skill. When not connected, those
  tools are removed from the run and a <persistence> system reminder tells
  the agent to use in-memory state (not localStorage) and inform the user
  that data won't persist beyond the preview tab.
- Auto-confirm signups (mailer_autoconfirm=true) at project provision —
  preview sandboxes can't honor the default email-redirect Site URL,
  so demos use email/password without an email-link round trip.
- hono-fullstack SKILL.md cross-refs updated; iframe-runtime vendor
  target follows the rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on env.parse

CI runs without .env, but env.ts validates required strings (DATABASE_URL,
SUPABASE_URL, LLM_API_KEY, …) at module load. Any test transitively
importing env (via crypto.ts → env.ts) crashed during test collection.

Set placeholder defaults for all required env vars after dotenv load —
local .env values still win, CI falls back to harmless test placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lhz960904
lhz960904 merged commit da3e465 into main May 10, 2026
1 check passed
@lhz960904
lhz960904 deleted the feat/day5-supabase-agent-tools branch May 10, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant