diff --git a/.gitignore b/.gitignore index 3945cd65..81dbe610 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ data/ debug/dist/ .cache/ .tsbuildinfo +website/.next/ +website/tsconfig.tsbuildinfo +website/node_modules/ diff --git a/docs/superpowers/plans/2026-04-27-boop-website.md b/docs/superpowers/plans/2026-04-27-boop-website.md new file mode 100644 index 00000000..81656e95 --- /dev/null +++ b/docs/superpowers/plans/2026-04-27-boop-website.md @@ -0,0 +1,1853 @@ +# Boop Agent Marketing Website Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a 2-page Next.js 14 marketing + documentation website in `website/` subdirectory of the boop-agent repo. + +**Architecture:** Landing page (`/`) with Hero, Architecture diagram, Features grid, Quickstart steps, and Prerequisites table. Docs page (`/docs`) with a fixed sidebar and all 5 markdown files fetched from GitHub raw URLs at build time rendered as a single scrollable page. All pages are SSG/ISR — no client-side data fetching. Client components are isolated to interactive widgets (Navbar hamburger, copy buttons, sidebar active tracking). + +**Tech Stack:** Next.js 14 App Router, TypeScript 5, Tailwind CSS v3, unified + remark-gfm + rehype-pretty-code (Shiki) for markdown, lucide-react for icons, next/font for Inter + JetBrains Mono. + +--- + +## File Map + +``` +website/ + package.json ← Next.js project deps (separate from root) + next.config.ts ← image domains, no extra config needed + tailwind.config.ts ← custom color theme + postcss.config.js ← autoprefixer + tsconfig.json ← strict TS, path aliases + app/ + layout.tsx ← root layout, fonts, full Metadata export + globals.css ← CSS vars, scrollbar, base styles + page.tsx ← landing page (server component) + sitemap.ts ← / and /docs routes + robots.ts ← allow all + docs/ + page.tsx ← docs page (async server component) + og/ + route.tsx ← OG image via ImageResponse (@vercel/og) + components/ + Navbar.tsx ← "use client" — hamburger state + Footer.tsx ← static server component + Hero.tsx ← static server component + FeaturesGrid.tsx ← static server component + ArchitectureBlock.tsx ← static server component + QuickstartBlock.tsx ← server component, contains CopyButton + PrerequisitesTable.tsx ← static server component + CopyButton.tsx ← "use client" — navigator.clipboard + DocsLayout.tsx ← "use client" — IntersectionObserver sidebar + DocSection.tsx ← server component, dangerouslySetInnerHTML + lib/ + docs.ts ← fetch + parse all 5 MD files, returns HTML + public/ + assets/ + boop.png ← downloaded from GitHub raw +``` + +--- + +## Task 1: Scaffold Next.js project + +**Files:** +- Create: `website/package.json` +- Create: `website/next.config.ts` +- Create: `website/tsconfig.json` +- Create: `website/postcss.config.js` + +- [ ] **Step 1: Create `website/package.json`** + +```json +{ + "name": "boop-agent-website", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "lucide-react": "^0.468.0", + "remark": "^15.0.1", + "remark-gfm": "^4.0.0", + "rehype": "^13.0.1", + "rehype-pretty-code": "^0.14.0", + "rehype-stringify": "^10.0.0", + "remark-rehype": "^11.1.0", + "shiki": "^1.0.0", + "unified": "^11.0.5" + }, + "devDependencies": { + "typescript": "^5.6.0", + "@types/react": "^18.3.0", + "@types/node": "^20.0.0", + "tailwindcss": "^3.4.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47" + } +} +``` + +- [ ] **Step 2: Create `website/next.config.ts`** + +```ts +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'raw.githubusercontent.com', + }, + ], + }, +} + +export default nextConfig +``` + +- [ ] **Step 3: Create `website/tsconfig.json`** + +```json +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { "@/*": ["./*"] } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} +``` + +- [ ] **Step 4: Create `website/postcss.config.js`** + +```js +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} +``` + +- [ ] **Step 5: Install dependencies** + +```bash +cd website && npm install +``` + +Expected: `node_modules/` created, no errors. + +- [ ] **Step 6: Commit** + +```bash +git add website/package.json website/next.config.ts website/tsconfig.json website/postcss.config.js +git commit -m "feat(website): scaffold Next.js 14 project" +``` + +--- + +## Task 2: Tailwind config + global CSS + fonts + +**Files:** +- Create: `website/tailwind.config.ts` +- Create: `website/app/globals.css` + +- [ ] **Step 1: Create `website/tailwind.config.ts`** + +```ts +import type { Config } from 'tailwindcss' + +const config: Config = { + content: [ + './app/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + ], + theme: { + extend: { + colors: { + bg: { + base: '#0e0e0e', + card: '#161616', + hover: '#1f1f1f', + }, + border: '#272727', + accent: { + DEFAULT: '#f97316', + dim: '#7c3515', + }, + text: { + primary: '#f0f0f0', + secondary: '#888888', + muted: '#555555', + }, + status: { + green: '#22c55e', + red: '#ef4444', + blue: '#3b82f6', + }, + }, + fontFamily: { + sans: ['var(--font-inter)', 'system-ui', 'sans-serif'], + mono: ['var(--font-jetbrains)', 'Menlo', 'monospace'], + }, + }, + }, + plugins: [], +} + +export default config +``` + +- [ ] **Step 2: Create `website/app/globals.css`** + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --font-inter: 'Inter', system-ui, sans-serif; + --font-jetbrains: 'JetBrains Mono', monospace; +} + +html { + scroll-behavior: smooth; +} + +body { + background-color: #0e0e0e; + color: #f0f0f0; + -webkit-font-smoothing: antialiased; +} + +/* Scrollbar */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: #0e0e0e; } +::-webkit-scrollbar-thumb { background: #272727; border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: #333; } + +/* Code blocks from rehype-pretty-code */ +pre { + background: #111 !important; + border: 1px solid #272727; + border-radius: 0.5rem; + overflow-x: auto; + padding: 1rem; +} + +code { + font-family: var(--font-jetbrains), monospace; + font-size: 0.875em; +} + +/* Inline code (not inside pre) */ +:not(pre) > code { + background: #161616; + border: 1px solid #272727; + border-radius: 0.25rem; + padding: 0.125rem 0.375rem; +} + +/* Focus rings */ +:focus-visible { + outline: 2px solid #f97316; + outline-offset: 2px; +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + html { + scroll-behavior: auto; + } +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add website/tailwind.config.ts website/app/globals.css +git commit -m "feat(website): add Tailwind theme config and global CSS" +``` + +--- + +## Task 3: Root layout + metadata + SEO files + +**Files:** +- Create: `website/app/layout.tsx` +- Create: `website/app/sitemap.ts` +- Create: `website/app/robots.ts` +- Create: `website/app/og/route.tsx` + +- [ ] **Step 1: Create `website/app/layout.tsx`** + +```tsx +import type { Metadata } from 'next' +import { Inter, JetBrains_Mono } from 'next/font/google' +import './globals.css' + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}) + +const jetbrainsMono = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-jetbrains', + display: 'swap', +}) + +export const metadata: Metadata = { + title: { default: 'Boop Agent', template: '%s | Boop Agent' }, + description: + 'A proactive iMessage-based personal agent built on the Claude Agent SDK. Multi-agent architecture, robust memory, automations, and 1000+ integrations via Composio.', + keywords: ['iMessage agent', 'Claude Agent SDK', 'AI agent', 'Composio', 'Convex', 'Sendblue', 'boop'], + authors: [{ name: 'Chris Raroque', url: 'https://github.com/raroque' }], + openGraph: { + type: 'website', + url: 'https://boop-agent.vercel.app', + title: 'Boop Agent — Your new best friend 🐶', + description: + 'A proactive iMessage-based personal agent built on the Claude Agent SDK.', + images: [{ url: '/og', width: 1200, height: 630 }], + }, + twitter: { + card: 'summary_large_image', + title: 'Boop Agent', + description: + 'A proactive iMessage-based personal agent built on the Claude Agent SDK.', + images: ['/og'], + }, + robots: { index: true, follow: true }, +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + Skip to content + + {children} + + + ) +} +``` + +- [ ] **Step 2: Create `website/app/sitemap.ts`** + +```ts +import type { MetadataRoute } from 'next' + +export default function sitemap(): MetadataRoute.Sitemap { + return [ + { + url: 'https://boop-agent.vercel.app', + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 1, + }, + { + url: 'https://boop-agent.vercel.app/docs', + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 0.8, + }, + ] +} +``` + +- [ ] **Step 3: Create `website/app/robots.ts`** + +```ts +import type { MetadataRoute } from 'next' + +export default function robots(): MetadataRoute.Robots { + return { + rules: { userAgent: '*', allow: '/' }, + sitemap: 'https://boop-agent.vercel.app/sitemap.xml', + } +} +``` + +- [ ] **Step 4: Create `website/app/og/route.tsx`** + +```tsx +import { ImageResponse } from 'next/og' + +export const runtime = 'edge' + +export async function GET() { + return new ImageResponse( + ( +
+
🐶
+
+ Boop Agent +
+
+ A proactive iMessage-based agent built on the Claude Agent SDK +
+
+ github.com/raroque/boop-agent +
+
+ ), + { width: 1200, height: 630 } + ) +} +``` + +- [ ] **Step 5: Run typecheck** + +```bash +cd website && npm run typecheck +``` + +Expected: no errors (may need `next-env.d.ts` generated — run `next build` or `next dev` first if missing). + +- [ ] **Step 6: Commit** + +```bash +git add website/app/layout.tsx website/app/sitemap.ts website/app/robots.ts website/app/og/route.tsx +git commit -m "feat(website): add root layout, metadata, sitemap, robots, OG image route" +``` + +--- + +## Task 4: Download boop.png asset + +**Files:** +- Create: `website/public/assets/boop.png` + +- [ ] **Step 1: Create public/assets directory and download boop.png** + +```bash +mkdir -p website/public/assets +curl -L "https://github.com/raroque/boop-agent/raw/main/assets/boop.png" \ + -o website/public/assets/boop.png +``` + +Expected: `website/public/assets/boop.png` file created (~some KB). + +- [ ] **Step 2: Verify file exists and is non-empty** + +```bash +ls -lh website/public/assets/boop.png +``` + +Expected: file size > 0 bytes. + +- [ ] **Step 3: Commit** + +```bash +git add website/public/assets/boop.png +git commit -m "feat(website): add boop mascot PNG asset" +``` + +--- + +## Task 5: CopyButton client component + +**Files:** +- Create: `website/components/CopyButton.tsx` + +- [ ] **Step 1: Create `website/components/CopyButton.tsx`** + +```tsx +'use client' + +import { useState } from 'react' +import { Copy, Check } from 'lucide-react' + +interface CopyButtonProps { + text: string + className?: string +} + +export function CopyButton({ text, className = '' }: CopyButtonProps) { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + if (typeof window === 'undefined') return + try { + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { + // Fallback for older browsers + const el = document.createElement('textarea') + el.value = text + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + } + + return ( + + ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/CopyButton.tsx +git commit -m "feat(website): add CopyButton client component" +``` + +--- + +## Task 6: Navbar component + +**Files:** +- Create: `website/components/Navbar.tsx` + +- [ ] **Step 1: Create `website/components/Navbar.tsx`** + +```tsx +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import Image from 'next/image' +import { Github, Youtube, Menu, X } from 'lucide-react' + +export function Navbar() { + const [open, setOpen] = useState(false) + + return ( +
+ + + {/* Mobile dropdown */} + {open && ( +
+
+ setOpen(false)} + > + Docs + + setOpen(false)} + > + GitHub + + setOpen(false)} + > + YouTube + +
+
+ )} +
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/Navbar.tsx +git commit -m "feat(website): add responsive Navbar component" +``` + +--- + +## Task 7: Footer component + +**Files:** +- Create: `website/components/Footer.tsx` + +- [ ] **Step 1: Create `website/components/Footer.tsx`** + +```tsx +import Image from 'next/image' +import Link from 'next/link' +import { Github, Youtube } from 'lucide-react' + +export function Footer() { + return ( + + ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/Footer.tsx +git commit -m "feat(website): add Footer component" +``` + +--- + +## Task 8: Hero section + +**Files:** +- Create: `website/components/Hero.tsx` + +- [ ] **Step 1: Create `website/components/Hero.tsx`** + +```tsx +import Image from 'next/image' +import { Star, Play } from 'lucide-react' + +export function Hero() { + return ( +
+ {/* Animated gradient mesh background */} +
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/Hero.tsx +git commit -m "feat(website): add Hero section with mascot, CTAs, and stat pills" +``` + +--- + +## Task 9: ArchitectureBlock component + +**Files:** +- Create: `website/components/ArchitectureBlock.tsx` + +- [ ] **Step 1: Create `website/components/ArchitectureBlock.tsx`** + +```tsx +import { Bot, Cpu, Database, Plug } from 'lucide-react' + +const ASCII_DIAGRAM = ` iMessage → Sendblue webhook → Interaction agent → Sub-agents (per task) + │ │ + ▼ ▼ + Memory store ←── Integrations (your MCP tools)` + +const COMPONENTS = [ + { + icon: Bot, + title: 'Interaction Agent', + description: + 'Lean dispatcher: reads memory, decides what to do, spawns focused sub-agents. Intentionally restricted — no web or file access.', + }, + { + icon: Cpu, + title: 'Execution Agents', + description: + 'Task-specific sub-agents with full tool access: WebSearch, WebFetch, integrations. One agent per task, spawned on demand.', + }, + { + icon: Database, + title: 'Memory (Convex)', + description: + 'Tiered short / long / permanent memory with post-turn extraction, decay, and a daily adversarial consolidation pipeline.', + }, + { + icon: Plug, + title: 'Integrations (Composio)', + description: + 'One API key unlocks 1000+ toolkits — Gmail, Slack, GitHub, Linear, Notion, and more. OAuth handled, no extra setup.', + }, +] + +export function ArchitectureBlock() { + return ( +
+
+

+ How it works +

+

+ A clean separation between receiving messages, deciding what to do, and doing the work. +

+ + {/* ASCII diagram */} +
+
+            {ASCII_DIAGRAM}
+          
+
+ + {/* Component cards */} +
+ {COMPONENTS.map(({ icon: Icon, title, description }) => ( +
+
+ +
+

{title}

+

{description}

+
+ ))} +
+
+
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/ArchitectureBlock.tsx +git commit -m "feat(website): add ArchitectureBlock with ASCII diagram and component cards" +``` + +--- + +## Task 10: FeaturesGrid component + +**Files:** +- Create: `website/components/FeaturesGrid.tsx` + +- [ ] **Step 1: Create `website/components/FeaturesGrid.tsx`** + +```tsx +import { + MessageSquare, + Terminal, + GitBranch, + Shield, + Layers, + Search, + RefreshCw, + Clock, + Send, + Heart, + Plug, + LayoutDashboard, +} from 'lucide-react' + +const FEATURES = [ + { + icon: MessageSquare, + title: 'iMessage in / out', + description: 'Receive and reply via iMessage with typing indicators and webhook deduplication.', + }, + { + icon: Terminal, + title: 'Sendblue CLI', + description: 'Auto-registers the inbound webhook on every restart — no manual re-pasting after ngrok URL rotation.', + }, + { + icon: GitBranch, + title: 'Dispatcher + workers', + description: 'A lean interaction agent spawns focused sub-agents that do the actual work.', + }, + { + icon: Shield, + title: 'Pure dispatcher', + description: 'Web access, files, and integrations are denied to the interaction agent — sub-agents only.', + }, + { + icon: Layers, + title: 'Tiered memory', + description: 'Short, long, and permanent tiers with post-turn extraction, decay, and cleaning.', + }, + { + icon: Search, + title: 'Vector search', + description: 'Semantic recall with Voyage or OpenAI embeddings — falls back to substring without a key.', + }, + { + icon: RefreshCw, + title: 'Memory consolidation', + description: 'Daily 3-phase adversarial pipeline (proposer → adversary → judge) that merges duplicates and prunes noise.', + }, + { + icon: Clock, + title: 'Automations', + description: 'Schedule recurring work from a text ("every morning at 8 summarize my calendar") and receive results via iMessage.', + }, + { + icon: Send, + title: 'Draft-and-send', + description: 'Any external action stages a draft first — the agent only commits when you confirm.', + }, + { + icon: Heart, + title: 'Heartbeat + retry', + description: 'Stuck agents auto-fail with timeout detection. Retry directly from the debug dashboard.', + }, + { + icon: Plug, + title: 'Composio integrations', + description: 'One API key unlocks 1000+ toolkits. Gmail, Slack, GitHub, Linear, Notion, and more — OAuth handled.', + }, + { + icon: LayoutDashboard, + title: 'Debug dashboard', + description: 'React + Vite dashboard with spend, tokens, agent timelines, memory graph, automations, and connections.', + }, +] + +export function FeaturesGrid() { + return ( +
+
+

+ Everything you need +

+

+ A full personal agent stack — not just a chatbot. +

+ +
+ {FEATURES.map(({ icon: Icon, title, description }) => ( +
+
+ +
+

{title}

+

{description}

+
+ ))} +
+
+
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/FeaturesGrid.tsx +git commit -m "feat(website): add FeaturesGrid with 12 feature cards" +``` + +--- + +## Task 11: QuickstartBlock component + +**Files:** +- Create: `website/components/QuickstartBlock.tsx` + +- [ ] **Step 1: Create `website/components/QuickstartBlock.tsx`** + +```tsx +import { CopyButton } from './CopyButton' +import { ExternalLink } from 'lucide-react' + +const STEPS = [ + { + n: 1, + title: 'Clone and install', + cmd: 'git clone https://github.com/raroque/boop-agent.git\ncd boop-agent\nnpm install', + }, + { + n: 2, + title: 'Install Claude Code', + cmd: 'npm install -g @anthropic-ai/claude-code', + }, + { + n: 3, + title: 'Run setup wizard', + cmd: 'npm run setup', + }, + { + n: 4, + title: 'Install ngrok', + cmd: 'npm install -g ngrok\nngrok config add-authtoken ', + }, + { + n: 5, + title: 'Start the agent', + cmd: 'npm run dev', + }, +] + +export function QuickstartBlock() { + return ( +
+
+

+ Get running in 5 steps +

+

+ The setup wizard handles API keys, webhook registration, and first-run validation. +

+ +
    + {STEPS.map(({ n, title, cmd }) => ( +
  1. +
    + {n} +
    +
    +

    {title}

    +
    +
    +                    {cmd}
    +                  
    +
    + +
    +
    +
    +
  2. + ))} +
+ + {/* Dashboard callout */} +
+ +
+

Debug dashboard

+

+ Visit{' '} + http://localhost:5173{' '} + after npm run dev to open the debug dashboard — agents, memory, automations, and connections. +

+
+
+
+
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/QuickstartBlock.tsx +git commit -m "feat(website): add QuickstartBlock with numbered steps and copy buttons" +``` + +--- + +## Task 12: PrerequisitesTable component + +**Files:** +- Create: `website/components/PrerequisitesTable.tsx` + +- [ ] **Step 1: Create `website/components/PrerequisitesTable.tsx`** + +```tsx +const PREREQS = [ + { + service: 'Sendblue', + href: 'https://sendblue.com/?utm_source=raroque', + why: 'iMessage bridge. Get a number, grab API keys.', + free: 'Free on their agent plan', + discount: 'RAROQUE20 — 20% off for 6 months', + }, + { + service: 'Convex', + href: 'https://convex.link/chrisraroque', + why: 'Database + realtime.', + free: 'Free tier is plenty', + discount: '—', + }, + { + service: 'Composio', + href: 'https://composio.dev/?utm_source=chris&utm_medium=youtube&utm_campaign=collab', + why: 'Integrations — one API key unlocks ~1000 toolkits. Optional for chat + memory only.', + free: 'Free tier covers personal use', + discount: 'CHRISXCOMPOSIO — 1 month free on starter plan', + }, + { + service: 'ngrok', + href: 'https://ngrok.com?ref=chrisraroque', + why: 'Expose your local port so Sendblue can reach it.', + free: 'Free tier works', + discount: '—', + }, +] + +export function PrerequisitesTable() { + return ( +
+
+

+ What you'll need +

+

+ All free-tier friendly. The setup wizard will prompt you for each key. +

+ +
+ + + + + + + + + + + {PREREQS.map(({ service, href, why, free, discount }, i) => ( + + + + + + + ))} + +
ServiceWhyFree?Discount
+ + {service} + + {why}{free}{discount}
+
+
+
+ ) +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add website/components/PrerequisitesTable.tsx +git commit -m "feat(website): add PrerequisitesTable component" +``` + +--- + +## Task 13: Landing page assembly + +**Files:** +- Create: `website/app/page.tsx` + +- [ ] **Step 1: Create `website/app/page.tsx`** + +```tsx +import { Navbar } from '@/components/Navbar' +import { Hero } from '@/components/Hero' +import { ArchitectureBlock } from '@/components/ArchitectureBlock' +import { FeaturesGrid } from '@/components/FeaturesGrid' +import { QuickstartBlock } from '@/components/QuickstartBlock' +import { PrerequisitesTable } from '@/components/PrerequisitesTable' +import { Footer } from '@/components/Footer' + +export default function HomePage() { + return ( + <> + +
+ + + + + +
+