As promised, Josh
Everything you need to start building real, live apps with AI β in one page. A short walkthrough, the exact tech stack I use, and copy-paste prompts and skills that make your Claude agent build like a pro from day one.
Start here
Watch this firstCan't see the video?
This intro walks you through the resources on this page. If it won't load, just start with the Tech Stack below and work your way down.
The tech stack
Three tools are all you need to take an idea to a live link people can actually visit. Everything here has a free tier that's more than enough to start.
Supabase
Your database and auth. Postgres + logins with a generous free tier β where your app's data lives. Only add it when your app actually needs to store or log in.
Vercel
Hosting. Push your code to GitHub, connect it to Vercel, and it deploys to a real URL you can share β automatically, every time you push.
HeroUI
Free is enoughYour UI component library. I bought Pro, but the free tier is more than enough β force your agents to always use it so your apps look sharp without hand-rolling components.
How I built this page
This site uses a few HeroUI Procomponents because I own Pro. You don't need them β here's the free @heroui/react swap for each: the sticky Navbar β a sticky header of Links Β· the floating page menu β a small anchor list Β· the tech cards β Card Β· the copy code blocks β a <pre> with a copy Button. Same look, all free.
Your everyday build prompt
Paste this every single time you start a build in Claude Code. It's the single habit that separates builds that ship from builds you throw away.
Go into plan mode & ask me rounds of clarifying questions for this build until you're 100% clear on the SOW. Use the advisor to review plan phases and give feedback before presenting the final plan. If we're building anything new, we must use HeroUI (/hero-ui) to match our design standards. Call this skill to review and analyze what you're building to select the proper components for use.Why this works
Plan-first stops the agent from coding the wrong thing. The review pass catches bad assumptions before they're baked in. HeroUI-first means you never hand-roll UI you'll rebuild later.
βThe advisorβ= a second, stronger-model review pass before you commit. No advisor tool wired up? Approximate it: after Claude drafts the plan, say βspin up a subagent to adversarially review this plan and report back.β Same effect.
Install the HeroUI skill (below in The HeroUI skill) once first β then the /hero-ui part of this prompt works.
Build a multi-Gmail MCP
Do this after the videoAn MCP is a little server that gives your agent new powers. This one lets your agent read and search email and manage your calendar across multiple Google accounts at once. Paste the prompt below into Claude Code after you've watched the walkthrough.
Go into plan mode and ask me rounds of clarifying questions until you're 100% clear on the SOW before writing any code. Review your plan critically (or spin up a subagent to adversarially review it) before presenting it to me.
GOAL
Build a local Model Context Protocol (MCP) server that gives my Claude agent access to MULTIPLE Google accounts at once β Gmail and Google Calendar to start, extensible to Contacts/Drive later. I want to say "check my personal and work inboxes" and have the agent hit both accounts through one server.
STACK
- TypeScript, Node 20+. @modelcontextprotocol/sdk (stdio transport). googleapis (official client).
- Per-account OAuth2 with offline refresh tokens. No service accounts.
CRITICAL ARCHITECTURE β TWO SEPARATE ENTRYPOINTS (do not merge these):
- `npm run auth -- --account <label>` = a SHORT-LIVED CLI I run in my own terminal. It does the interactive OAuth (opens the browser, runs a localhost callback server, exchanges the code, writes tokens), then EXITS.
- `node dist/server.js` = the MCP stdio server. It NEVER does interactive OAuth, never opens a browser or a listener β it only reads stored tokens and refreshes them silently. If an account has no token, its tools fail with "run: npm run auth -- --account <label>".
- WHY: interactive OAuth cannot run inside a stdio MCP process β a browser wait / listener blocks the JSON-RPC loop and corrupts the protocol. Keep them separate.
STDOUT HYGIENE (get this wrong = MCP silently won't connect):
- The server's STDOUT is the JSON-RPC channel ONLY. Every log/banner/warning/debug line goes to STDERR (console.error). One stray console.log β or a googleapis "visit this URL" print β on stdout corrupts the handshake.
GOOGLE CLOUD SETUP (put in the README, step by step):
- One Google Cloud project. Enable Gmail API + Google Calendar API.
- OAuth 2.0 Client, type "Desktop app". Download the client JSON.
- Consent screen: User type External. Set publishing status to "In production" β NOT "Testing". (Testing-mode apps expire refresh tokens after ~7 days, so the tool would silently die a week later. Production + unverified is fine for your own personal accounts; you'll click through a one-time "Google hasn't verified this app -> Advanced -> Continue".)
- Sensitive scopes: list the full scope URIs on the consent screen's Data Access page.
OAUTH FLOW (in the auth CLI):
- Auth URL params: access_type=offline & prompt=consent & include_granted_scopes=true. (prompt=consent forces a refresh_token each run; access_type=offline alone often returns none on re-consent.)
- Redirect URI: a FIXED loopback port you ALSO register on the Desktop client, e.g. http://127.0.0.1:53682/oauth/callback (use 127.0.0.1 and match it exactly). Random `state` param, verified. 2-minute timeout. Fallback: if the browser won't open, print the URL and accept a pasted redirect URL/code.
- Token write rule: MERGE β if a refresh response omits refresh_token, KEEP the existing one; NEVER overwrite a stored refresh_token with null/undefined (that kills the account). After OAuth, call oauth2.userinfo.get and store the account's real email next to its label.
SCOPES (least privilege β use the full URIs):
- https://www.googleapis.com/auth/gmail.readonly
- https://www.googleapis.com/auth/gmail.send
- https://www.googleapis.com/auth/calendar.events
- https://www.googleapis.com/auth/userinfo.email
Ask me before adding gmail.modify or any broader/restricted scope.
TOKEN STORAGE:
- Absolute config dir, NOT in the project: ~/.config/gmail-mcp/tokens.json, chmod 600, git-ignored. Shape:
{ "personal": { "email": "...", "refresh_token": "...", "access_token": "...", "expiry_date": 0 }, "work": { ... } }
- Auto-refresh expired access tokens per call and PERSIST the rotated token. On invalid_grant -> return "re-run npm run auth -- --account <label>".
- NEVER put tokens/secrets in tool results or thrown error messages.
TOOLS (every tool takes a REQUIRED `account` label routed to that account's creds; unknown account -> error listing list_accounts):
- list_accounts() -> configured labels + their emails.
- gmail_search(account, query, max) -> message ids + snippets (Gmail search syntax); return nextPageToken.
- gmail_read(account, message_id) -> from/to/subject/date + PLAIN-TEXT body (walk payload.parts, base64url-decode, prefer text/plain, strip HTML β never return raw base64).
- gmail_send(account, to, subject, body, cc?, bcc?) -> SAFETY: default to creating a DRAFT, or require an explicit confirm:true arg; never send silently. (An agent that reads untrusted inbound email AND can send is a prompt-injection exfiltration path β name this risk in the README.)
- calendar_list_events(account, time_min, time_max, max, calendarId=primary) -> start/end/title/location.
- calendar_create_event(account, title, start, end, attendees?, description?, calendarId=primary) -> id + htmlLink. Require ISO-8601 WITH timezone offset (or an explicit timeZone) β never naive local times.
QUALITY BARS:
- Validate every tool's inputs; return clear error strings (never crash the server). Handle Google 429/403 with a short backoff.
- Do NOT implement Contacts/Drive in v1 β leave a clean extension point.
REGISTER IN CLAUDE CODE (README, with real values):
npm run build # produces dist/
claude mcp add gmail -s user \
--env GOOGLE_OAUTH_CLIENT_FILE=$HOME/.config/gmail-mcp/client.json \
--env GMAIL_MCP_CONFIG_DIR=$HOME/.config/gmail-mcp \
-- node /ABSOLUTE/PATH/to/dist/server.js
# restart Claude Code, then run list_accounts as a smoke test.
- Env MUST reach the server via `--env` (Claude starts it from a different cwd, so a project .env won't load). Use absolute paths everywhere.
ACCEPTANCE TESTS (must pass):
1. `npm run auth` for two accounts -> tokens.json has both with emails + refresh_tokens.
2. Restart Claude Code -> list_accounts returns both labels.
3. gmail_search on each returns real results; gmail_read returns readable plain text.
4. calendar_list_events returns upcoming events for each account.
5. gmail_send with no confirm -> creates a draft (does NOT send).
NOTE: no UI here β the HeroUI rule doesn't apply (backend MCP server). Keep it small, typed, secure.The HeroUI skill
Drop this into .claude/skills/hero-ui/SKILL.md in any project, then type /hero-ui before you call any UI 'done.' It keeps your agent from hand-rolling components β it maps everything to a real HeroUI component instead. Works with the free @heroui/react; no Pro needed.
---
name: hero-ui
description: Guardrail that enforces "always build UI with HeroUI β never hand-roll from scratch." Maps every interactive/semantic element to a real HeroUI component, composes from primitives when nothing exact fits, and blocks (with an escape hatch) when even composition can't. Modes: review (default) / plan / snippet, plus --fix. Use before considering any React/Next UI done.
user-invocable: true
argument-hint: "[review|plan|snippet] [path] [--fix]"
---
# /hero-ui β HeroUI Conformance Guardrail
Enforces one rule: **UI is built with HeroUI, never hand-rolled from scratch.** The HeroUI docs are the
sole authority for what components/props exist β this skill NEVER invents a component or prop from memory.
Authority source, in order:
1. If you have the **HeroUI MCP** configured, use it (`list_components`, `get_component_docs`) β it's the
live catalog.
2. Otherwise use the **live docs at https://www.heroui.com/docs** as the authority. Never guess a
component or prop name; look it up.
## HeroUI v3 β hard setup requirements (get these wrong and nothing renders)
- Requires **Tailwind CSS v4** (NOT v3). - **No Provider needed** β components work directly, no wrapper.
- **Compound components** β e.g. `Tabs.List`, `Card.Body`, `Navbar.Brand` (namespaced parts).
- Interactive props use **`onPress`**, not `onClick`. HeroUI components are **client components** β the
file needs `"use client"` at the top in Next.js App Router or the app crashes.
## Modes
- **review** (default) β audit code for conformance. Target = the given path, or the git diff of
changed/staged `*.tsx|*.jsx` when no path. `--repo` = whole-repo audit.
- **plan** β map INTENDED UI (a described screen / a plan doc) to HeroUI components before any code
exists. Output = a mapping table, not a pass/fail.
- **snippet** β print this exact block (paste into a plan prompt or a project's CLAUDE.md so builds start
HeroUI-first):
> "Build all UI with HeroUI (`@heroui/react`), never hand-rolled. Requires Tailwind v4, no Provider,
> compound components, `onPress` not onClick, `"use client"` on interactive files. Before writing a
> component, look it up in the HeroUI catalog (MCP or heroui.com/docs) β never invent a name or prop.
> Map each element to an exact HeroUI component; compose from <=3 primitives only if nothing exact fits."
## Step 1 β Confirm scope
- Confirm it's a React project (`*.tsx|*.jsx` + `react` in package.json). If not, say so and stop.
- Skip (never flag): React Native, email HTML, MDX prose, canvas/WebGL/Three.js, chart-render internals,
`*.stories.*`, generated dirs (`.next dist build node_modules`).
## Step 2 β Catalog
Pull the component list from the authority (MCP `list_components` or the docs sidebar). Map display names
to imports (`button` -> `Button`, `text-field` -> `TextField`). Note the allow-native set β HeroUI itself
uses native `<img>` and an icon library; never false-flag those.
## Step 3 β Detect (deterministic)
Scan for elements that should be HeroUI but aren't:
1. Raw interactive tags (`<button> <input> <select> <dialog> <textarea>`) not from HeroUI.
2. Role/handler on a non-HeroUI tag (`<div role="button" onClick>`).
3. `styled.button` / styled-native primitives.
4. `<Box as="button">` dynamic-tag interactives.
5. A foreign UI lib (`@mantine`, `@chakra`, `antd`, MUI, etc.).
6. A local shadcn-style `components/ui/*` reimplementation of something HeroUI already has.
Record each as a suspect {file, line, element}.
## Step 4 β Fit ladder (authority-grounded, deduped)
For each unique element, walk the ladder:
1. β
**Exact** β a HeroUI component clearly maps (`<button>`->`Button`, `<input>`->`Input`/`TextField`,
`<select>`->`Select`, `<dialog>`->`Modal`). Look up the real docs and report component + import line +
a real prop example. HeroUI uses `onPress`, not `onClick` β note that.
2. π§© **Compose (cap 3)** β no single exact component, but buildable from <=3 HeroUI primitives matching a
documented pattern. More than that -> don't build a Frankenstein; recommend a redesign or the escape
hatch. A composite is a PARTIAL, never a full pass.
3. β **Block** β nothing fits even composed. Push back with a redesign-to-fit suggestion or an explicit
allow entry.
**Theme tokens (severity-tiered):**
- **P0** β a hard-coded color literal (`#hex`, `rgb(`, `hsl(`) where a theme color token exists.
- **P1** β arbitrary radius/spacing (`rounded-[7px]`, `p-[13px]`) bypassing the theme scale.
- **P2** β normal Tailwind utilities / layout / `var(--β¦)` -> allowed, never flagged.
Only flag CSS that reimplements a component's chrome, not layout.
## Step 5 β Verdict
Per-element table: β
Component (+import +prop example) Β· π§© composite (partial) Β· β block Β· β allowlisted.
Then a top-line:
- **FULL_PASS** β every flagged element maps to an exact HeroUI component and tokens are clean.
- **COMPONENT_PASS** β components conform but there are open P0/P1 token items, or coverage is thin.
- **NON_CONFORMANT: N** β real violations, all exact-fixable. Name the swaps; point at `--fix`.
- **BLOCKED: N** β >=1 unresolved (β or π§© not allowlisted). List them with next actions.
If you truly cannot confirm a component/prop from the authority (MCP or docs both unreachable), do NOT
emit FULL_PASS and do NOT invent props β cap at COMPONENT_PASS and say the catalog was unavailable.
## Step 6 β `--fix` (only when the flag is passed)
Exact (β
) swaps ONLY β never auto-fix composites or blocks. Per file, patch-report first, apply on confirm.
- HeroUI components are client components: if you swap into a file with no `"use client"`, add it and warn.
- Preserve behavior: keep handlers/state; map events (`onClick`->`onPress`); flag anything you can't map.
- After: run `tsc --noEmit` on touched files. Only claim it works if you actually ran it in a browser;
otherwise say "swapped, not browser-verified."
## Escape hatch
When something legitimately can't be HeroUI (a third-party embed, a canvas, etc.), mark it:
`{/* heroui-ignore: <reason> */}` inline. Reviewed elements with that marker drop to β allowlisted.
## Prevention
Recommend (don't silently add) a one-line rule in the project's CLAUDE.md: "All UI is built with HeroUI β
run /hero-ui before any UI is done." The `plan`/`snippet` modes do the preventive work; `review` is the catch.Give your agent a memory
Set up an Obsidian vault as long-term memory so your agent remembers who you are and what you've decided across sessions. Paste the setup prompt, then install the three skills below it.
A routing index the agent reads at the start of every session. Pointers only.
Individual notes it reads only when they're relevant to what you're doing.
When it's not sure which note, it greps the vault. Plain search, no magic.
Help me set up an Obsidian vault as long-term memory for my Claude agent, so it remembers who I am and what we've decided across sessions. Go into plan mode, ask me clarifying questions, then build it.
THE IDEA (3 tiers)
- Tier 1 (always loaded): a routing index the agent reads at the start of every session. POINTERS only, never content.
- Tier 2 (on demand): individual memory notes the agent reads only when they're relevant.
- Tier 3 (search): when it needs something and doesn't know which note, it greps the vault. This is plain ripgrep over the memory folder β NOT semantic/vector search. (You can add embeddings later; don't pretend you have it now.) A tiny /recall skill (below) does this.
STRUCTURE (create exactly this)
- ~/memory-vault/ <- the Obsidian vault (pick a real absolute path; if you use ~, expand it to your home dir in the skills)
- ~/memory-vault/MEMORY.md <- Tier 1 index (template below). Hard cap ~80 lines.
- ~/memory-vault/memory/ <- the notes
- ~/memory-vault/memory/recent.md <- a rolling "what happened last few sessions" cache (state, not knowledge)
CREATE MEMORY.md WITH THIS EXACT SKELETON (so /boot knows where to look):
# Memory Index (pointers only β never content)
## Always loaded β identity
- [About Me](memory/user_about_me.md) β who I am
## Always loaded β core rules
(empty until your first feedback note)
## User
## Feedback
## Projects
## Reference
Also SEED two files so boot has something real on day one:
- memory/user_about_me.md β a short note about me (ask me 3-4 questions to fill it: name, what I do, how I like to work, current focus).
- memory/recent.md β just a header stub: "# Recent sessions (newest first)".
NOTE TYPES (name files by type β always use these exact prefixes)
- user_*.md β who I am: role, preferences, how I work, expertise.
- feedback_*.md β corrections/confirmations you gave me, WITH the why and how-to-apply.
- project_*.md β decisions, goals, timelines, what shipped (use absolute dates, not "next week").
- reference_*.md β external systems: tools, URLs, dashboards, accounts.
Each note has frontmatter: name, description (one line, used for relevance), type, created, updated.
THEN create THREE skills for me (full text below): /boot (load memory at session start), /save (capture new memory), /recall (grep the vault). Install them at ~/.claude/skills/<name>/SKILL.md (user scope, so they work in every project), and add ONE line to your project's CLAUDE.md: "At the start of every session, run /boot before anything else." (Nothing auto-runs boot β this line is what makes memory actually load.) Use the SAME vault path + index name across all three skills.The three skills it creates:
---
name: boot
description: Load agent memory at the start of a session. Use when I say 'boot', 'good morning', or at the start of any session.
user-invocable: true
---
# Boot Sequence
Load memory in order, then greet. Resolve every path against the vault root `~/memory-vault/`.
## Step 1 β Routing index
Read `~/memory-vault/MEMORY.md`. This tells you what memories exist. Do NOT read every file it points
to β most are routing only.
## Step 2 β Identity
Read the identity note(s) listed at the top of MEMORY.md (the `user_*.md` files). These are always loaded.
## Step 3 β Always-loaded rules
Read every file MEMORY.md lists under an "Always loaded" / "Core rules" section. These are the behavioral
defaults for the session. Everything else in MEMORY.md is routing only β read on demand when a note's
one-line description is relevant to the current task.
## Step 4 β Recent state
Read `~/memory-vault/memory/recent.md` β the last few sessions' state so you start warm. Read it silently;
don't dump it back to me. Treat any "waiting on me" item as a lead to verify, not current truth (it may be
resolved already).
## Step 5 β Greet
Greet me in one line and ask what we're working on. Nothing else β no summary of what you loaded.
Rule: if a file is missing, note it silently and continue. Never dump loaded content back to me.