@oaklandzoo/ostup 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/bin/cli.mjs +27 -1
- package/package.json +1 -1
- package/src/brief/classify.mjs +218 -0
- package/src/brief/index.mjs +126 -0
- package/src/brief/profile-router.mjs +89 -0
- package/src/brief/questions.mjs +333 -0
- package/src/brief/render-brief.mjs +232 -0
- package/src/brief/sample-briefs.mjs +304 -0
- package/src/brief/schema.mjs +176 -0
- package/src/mvp-flow.mjs +43 -1
- package/src/templates.mjs +4 -0
- package/templates/.claude/commands/break-into-stories.md +101 -0
- package/templates/.claude/commands/handoff-doctor.md +93 -0
- package/templates/.claude/commands/resume.md +102 -0
- package/templates/AGENTS.md +13 -2
- package/templates/CLAUDE.md +3 -0
- package/templates/START_HERE.md +11 -4
- package/templates/profiles/blog/.env.example.additions +7 -0
- package/templates/profiles/blog/README.md +70 -0
- package/templates/profiles/blog/section-prompts.md +63 -0
- package/templates/profiles/booking/.env.example.additions +16 -0
- package/templates/profiles/booking/README.md +61 -0
- package/templates/profiles/booking/section-prompts.md +47 -0
- package/templates/profiles/lead-gen/.env.example.additions +8 -0
- package/templates/profiles/lead-gen/README.md +58 -0
- package/templates/profiles/lead-gen/section-prompts.md +47 -0
- package/templates/profiles/marketing/README.md +39 -0
- package/templates/profiles/marketing/section-prompts.md +36 -0
- package/templates/profiles/saas-dashboard/.env.example.additions +21 -0
- package/templates/profiles/saas-dashboard/README.md +60 -0
- package/templates/profiles/saas-dashboard/section-prompts.md +52 -0
- package/templates/scripts/verify.sh +128 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Profile: marketing
|
|
2
|
+
|
|
3
|
+
> Single-page or short marketing site. Default for projects with weak signals.
|
|
4
|
+
|
|
5
|
+
## Day-one scope
|
|
6
|
+
|
|
7
|
+
| Section | Purpose | Required |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Hero | Single primary value statement + one CTA (email or "Get started") | yes |
|
|
10
|
+
| Features | 3-6 short benefit cards | yes |
|
|
11
|
+
| Social proof / Testimonials | Quote, logo strip, or "in use at" line | optional |
|
|
12
|
+
| Pricing | One or two tiers, simple | optional |
|
|
13
|
+
| Email signup | Inline form posting to /api/subscribe (no DB; logs or forwards) | optional |
|
|
14
|
+
| Footer | Links, year, attribution | yes |
|
|
15
|
+
|
|
16
|
+
## Wired infrastructure
|
|
17
|
+
|
|
18
|
+
None by default. Pure static.
|
|
19
|
+
|
|
20
|
+
If the brief includes `email` add-on (newsletter signup), the agent should wire Resend or a generic webhook for the form submission.
|
|
21
|
+
|
|
22
|
+
## What ships in this overlay
|
|
23
|
+
|
|
24
|
+
- `section-prompts.md` — concrete guidance for each section.
|
|
25
|
+
- Anything in `.additions` is appended to the matching scaffolded file.
|
|
26
|
+
|
|
27
|
+
## Hard rules
|
|
28
|
+
|
|
29
|
+
- Mobile-first. Test at 420x900 with `scripts/screenshot.sh`.
|
|
30
|
+
- No third-party tracking. No popups. No chat widget.
|
|
31
|
+
- One CTA per page. If a section has its own CTA, route it to the same destination as the hero CTA.
|
|
32
|
+
- All copy comes from `docs/brief.md`. The agent paraphrases for the page, never invents new claims.
|
|
33
|
+
|
|
34
|
+
## Acceptance
|
|
35
|
+
|
|
36
|
+
- Hero readable at 420x900.
|
|
37
|
+
- Single primary CTA visible above the fold.
|
|
38
|
+
- Lighthouse mobile Performance >= 90, Accessibility >= 95.
|
|
39
|
+
- No console errors.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Marketing profile section prompts
|
|
2
|
+
|
|
3
|
+
Agent: build each section using the brief in `docs/brief.md`. Do not invent claims.
|
|
4
|
+
|
|
5
|
+
## Hero
|
|
6
|
+
|
|
7
|
+
- 1 headline (5-9 words) drawn from `project.summary`.
|
|
8
|
+
- 1 subhead (one sentence) describing who it is for and what changes.
|
|
9
|
+
- 1 primary CTA button. Text is imperative, e.g. "Get the early access link" or "Start free."
|
|
10
|
+
- Optional: small visual (an SVG mark from `inputs/images/`, NOT a generic stock photo).
|
|
11
|
+
|
|
12
|
+
## Features (3-6 cards)
|
|
13
|
+
|
|
14
|
+
- Each card has: short title (3-5 words) + one-sentence description.
|
|
15
|
+
- Pull titles from `scope.must_have_sections` (skip "hero" and "footer").
|
|
16
|
+
- Description echoes the brief vibe in `brand.vibe`.
|
|
17
|
+
- Use a grid; on mobile stack 1 column.
|
|
18
|
+
|
|
19
|
+
## Pricing (if included)
|
|
20
|
+
|
|
21
|
+
- 1-3 tiers. Each tier: name, price (or "free"), 3-5 bullets, single CTA.
|
|
22
|
+
- Use the pricing notes in `business_model.pricing_notes`.
|
|
23
|
+
- If no pricing info in brief, omit the section entirely. Do not invent prices.
|
|
24
|
+
|
|
25
|
+
## Email signup
|
|
26
|
+
|
|
27
|
+
- One field (email) + one button.
|
|
28
|
+
- Submit to `/api/subscribe` (or `/api/contact`).
|
|
29
|
+
- Inline success message on submit: "You're on the list."
|
|
30
|
+
- No double opt-in flow v1.
|
|
31
|
+
|
|
32
|
+
## Footer
|
|
33
|
+
|
|
34
|
+
- Copyright year (current).
|
|
35
|
+
- 2-4 links max: GitHub, npm, docs, contact.
|
|
36
|
+
- No social icons unless the brief says social presence matters.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# --- saas-dashboard profile additions ---
|
|
2
|
+
# Auth (Better Auth)
|
|
3
|
+
BETTER_AUTH_SECRET=
|
|
4
|
+
BETTER_AUTH_URL=http://localhost:3000
|
|
5
|
+
|
|
6
|
+
# Database (Neon recommended for Postgres)
|
|
7
|
+
DATABASE_URL=
|
|
8
|
+
|
|
9
|
+
# Stripe subscriptions
|
|
10
|
+
STRIPE_SECRET_KEY=
|
|
11
|
+
STRIPE_PUBLISHABLE_KEY=
|
|
12
|
+
STRIPE_WEBHOOK_SECRET=
|
|
13
|
+
STRIPE_PRICE_ID_SOLO=
|
|
14
|
+
STRIPE_PRICE_ID_TEAM=
|
|
15
|
+
|
|
16
|
+
# Public app URL
|
|
17
|
+
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
18
|
+
|
|
19
|
+
# Email (for verification + password reset)
|
|
20
|
+
RESEND_API_KEY=
|
|
21
|
+
AUTH_FROM_EMAIL=noreply@example.com
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Profile: saas-dashboard
|
|
2
|
+
|
|
3
|
+
> SaaS MVP with auth, subscription billing skeleton, dashboard shell, settings. Pick the workflow over feature depth.
|
|
4
|
+
|
|
5
|
+
## Day-one scope
|
|
6
|
+
|
|
7
|
+
| Section | Purpose | Required |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Landing hero | Convert visitor to signup | yes |
|
|
10
|
+
| Pricing | 1-3 tiers, monthly + annual toggle | yes |
|
|
11
|
+
| Sign up / Log in | Better Auth flow (email+password or magic link) | yes |
|
|
12
|
+
| Dashboard shell | Authenticated route, empty state with onboarding hint | yes |
|
|
13
|
+
| Settings | Profile + billing pages | yes |
|
|
14
|
+
| Billing | Stripe Customer Portal link OR a stub for "manage subscription" | yes |
|
|
15
|
+
| Footer | Links, year, GitHub, status page link if any | yes |
|
|
16
|
+
|
|
17
|
+
## Wired infrastructure
|
|
18
|
+
|
|
19
|
+
- **Better Auth** for authentication. Email+password v1; magic link v2.
|
|
20
|
+
- **Postgres** for `User` + any product entities from the brief.
|
|
21
|
+
- **Stripe** for subscriptions. Use Stripe Checkout for initial signup, Stripe Customer Portal for management.
|
|
22
|
+
- **Middleware** at the route level: unauthenticated visitors to `/dashboard/*` redirect to `/login`.
|
|
23
|
+
|
|
24
|
+
## Env additions
|
|
25
|
+
|
|
26
|
+
See `.env.example.additions`.
|
|
27
|
+
|
|
28
|
+
## API contracts
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
POST /api/auth/signup (Better Auth handles)
|
|
32
|
+
POST /api/auth/login (Better Auth handles)
|
|
33
|
+
POST /api/auth/logout (Better Auth handles)
|
|
34
|
+
GET /api/auth/session (Better Auth handles; returns user or 401)
|
|
35
|
+
|
|
36
|
+
POST /api/billing/checkout Body: { plan: 'solo' | 'team' }
|
|
37
|
+
Response: { url } (Stripe Checkout URL)
|
|
38
|
+
|
|
39
|
+
GET /api/billing/portal Response: { url } (Stripe Customer Portal URL)
|
|
40
|
+
|
|
41
|
+
POST /api/webhooks/stripe Handles subscription.created, .updated, .deleted
|
|
42
|
+
Updates User.plan
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Hard rules
|
|
46
|
+
|
|
47
|
+
- Auth gate runs at middleware level. Never serve dashboard UI to unauthenticated requests, even briefly.
|
|
48
|
+
- Session cookies are httpOnly, secure in production, sameSite=lax.
|
|
49
|
+
- Stripe webhook signature verification is mandatory. Reject on bad sig.
|
|
50
|
+
- Empty dashboard state has a clear "Get started" path. No dead-end empty UI.
|
|
51
|
+
- All errors during auth flow are user-facing (not stack traces).
|
|
52
|
+
- Settings → Billing links to Stripe Customer Portal, not a custom billing UI v1.
|
|
53
|
+
|
|
54
|
+
## Acceptance
|
|
55
|
+
|
|
56
|
+
- New user can sign up, see empty dashboard, log out, log back in.
|
|
57
|
+
- Stripe Checkout can be initiated; webhook updates user plan.
|
|
58
|
+
- Visiting `/dashboard` unauthenticated redirects to `/login`.
|
|
59
|
+
- Visiting `/login` while logged in redirects to `/dashboard`.
|
|
60
|
+
- Lighthouse landing-page Performance >= 90; dashboard >= 80 (slightly lower acceptable due to auth code).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SaaS dashboard profile section prompts
|
|
2
|
+
|
|
3
|
+
Agent: build each section using the brief. Default auth library: Better Auth. Default DB: Postgres (Neon if no preference).
|
|
4
|
+
|
|
5
|
+
## Landing hero (unauthenticated)
|
|
6
|
+
|
|
7
|
+
- Single big claim: the change the product delivers (echo `project.summary`).
|
|
8
|
+
- One CTA: "Start free" or "Get started" → routes to `/signup`.
|
|
9
|
+
- Optional logo strip or "Used by" line if the brief mentions customers.
|
|
10
|
+
|
|
11
|
+
## Pricing
|
|
12
|
+
|
|
13
|
+
- 1-3 tiers from `business_model.pricing_notes`. If 1 tier: show it plus "Custom for teams >".
|
|
14
|
+
- Monthly + annual toggle. Annual = 2 months free (standard pattern).
|
|
15
|
+
- Each tier: name, price, 4-6 bullets, single CTA "Start free" or "Upgrade" depending on auth state.
|
|
16
|
+
|
|
17
|
+
## Sign up / Log in
|
|
18
|
+
|
|
19
|
+
- Two-tab UI on `/auth` OR separate `/signup` and `/login` routes.
|
|
20
|
+
- Email + password v1.
|
|
21
|
+
- Clear error states: "Email already in use", "Wrong password", "Too many attempts" (rate-limit).
|
|
22
|
+
- After signup: email verification flow (Resend).
|
|
23
|
+
- After login: redirect to `/dashboard`.
|
|
24
|
+
|
|
25
|
+
## Dashboard shell
|
|
26
|
+
|
|
27
|
+
- Sidebar nav (collapsible on mobile): Home, Settings, Billing, Logout.
|
|
28
|
+
- Header bar: user email + dropdown.
|
|
29
|
+
- Empty state: "Welcome to {{DISPLAY_NAME}}. Start by [first action from brief]."
|
|
30
|
+
- One primary action button in the empty state.
|
|
31
|
+
|
|
32
|
+
## Settings
|
|
33
|
+
|
|
34
|
+
- Two sub-pages: Profile, Billing.
|
|
35
|
+
- Profile: name, email (verified), change password.
|
|
36
|
+
- Billing: link to Stripe Customer Portal via `/api/billing/portal`. Show current plan + next renewal date.
|
|
37
|
+
|
|
38
|
+
## Billing
|
|
39
|
+
|
|
40
|
+
- Stripe Checkout via `/api/billing/checkout`. Redirect on success to `/dashboard?welcome=1`.
|
|
41
|
+
- Stripe Customer Portal for management.
|
|
42
|
+
- Webhook updates `User.plan` field.
|
|
43
|
+
|
|
44
|
+
## Footer
|
|
45
|
+
|
|
46
|
+
- Privacy, Terms, Status, GitHub.
|
|
47
|
+
- Year, copyright.
|
|
48
|
+
- No SSO / SAML claims v1 unless brief says so explicitly.
|
|
49
|
+
|
|
50
|
+
## Hard rule
|
|
51
|
+
|
|
52
|
+
- Middleware-level auth gate. Use Next.js middleware to protect `/dashboard/*` and `/settings/*`. Unauthenticated requests redirect to `/login`. Authenticated requests to `/login` redirect to `/dashboard`.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Profile-aware verification gate. Runs the right checks for the project's profile.
|
|
3
|
+
# Called after /update-image, /update-gui, /update-backend, or at any time the operator wants to verify.
|
|
4
|
+
#
|
|
5
|
+
# Usage: scripts/verify.sh [profile] [deploy_url]
|
|
6
|
+
# Reads: docs/brief.json (for profile) if no profile arg
|
|
7
|
+
# Defaults: profile=marketing, deploy_url from .vercel/project.json if available
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
PROFILE="${1:-}"
|
|
12
|
+
DEPLOY_URL="${2:-}"
|
|
13
|
+
|
|
14
|
+
# Auto-detect profile from brief.json if not given
|
|
15
|
+
if [ -z "$PROFILE" ] && [ -f docs/brief.json ]; then
|
|
16
|
+
PROFILE=$(python3 -c "import json; print(json.load(open('docs/brief.json'))['scaffold']['profile'])" 2>/dev/null || echo "marketing")
|
|
17
|
+
fi
|
|
18
|
+
PROFILE="${PROFILE:-marketing}"
|
|
19
|
+
|
|
20
|
+
# Auto-detect deploy URL if not given (best-effort; operator may need to pass it)
|
|
21
|
+
if [ -z "$DEPLOY_URL" ]; then
|
|
22
|
+
if [ -f .vercel/project.json ]; then
|
|
23
|
+
PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.vercel/project.json'))['projectName'])" 2>/dev/null || echo "")
|
|
24
|
+
if [ -n "$PROJECT_NAME" ]; then
|
|
25
|
+
DEPLOY_URL="https://${PROJECT_NAME}.vercel.app"
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
echo "=========================================="
|
|
31
|
+
echo " Verifying profile: $PROFILE"
|
|
32
|
+
echo " Deploy URL: ${DEPLOY_URL:-<not detected; pass as arg 2>}"
|
|
33
|
+
echo "=========================================="
|
|
34
|
+
|
|
35
|
+
PASS=0
|
|
36
|
+
FAIL=0
|
|
37
|
+
|
|
38
|
+
check() {
|
|
39
|
+
local desc="$1"
|
|
40
|
+
local cmd="$2"
|
|
41
|
+
echo -n " [$PROFILE] $desc ... "
|
|
42
|
+
if eval "$cmd" > /tmp/verify-out 2>&1; then
|
|
43
|
+
echo "PASS"
|
|
44
|
+
PASS=$((PASS + 1))
|
|
45
|
+
else
|
|
46
|
+
echo "FAIL"
|
|
47
|
+
echo " Output: $(head -3 /tmp/verify-out)"
|
|
48
|
+
FAIL=$((FAIL + 1))
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# === Common checks (all profiles) ===
|
|
53
|
+
echo ""
|
|
54
|
+
echo "--- common ---"
|
|
55
|
+
check "build succeeds" "npm run build"
|
|
56
|
+
check ".env.example exists" "[ -f .env.example ]"
|
|
57
|
+
check "no {{TOKEN}} placeholders in CLAUDE.md" "! grep -E '\\{\\{[A-Z_]+\\}\\}' CLAUDE.md || true"
|
|
58
|
+
|
|
59
|
+
if [ -n "$DEPLOY_URL" ]; then
|
|
60
|
+
check "live URL returns 200" "curl -sI '$DEPLOY_URL' | head -1 | grep -E '200|301|302'"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# === Profile-specific checks ===
|
|
64
|
+
case "$PROFILE" in
|
|
65
|
+
lead-gen)
|
|
66
|
+
echo ""
|
|
67
|
+
echo "--- lead-gen ---"
|
|
68
|
+
check "/api/contact route exists" "[ -f src/app/api/contact/route.ts ] || [ -f src/app/api/contact/route.js ] || [ -f app/api/contact/route.ts ]"
|
|
69
|
+
check "JSON-LD LocalBusiness in HTML" "grep -r 'LocalBusiness' src/ 2>/dev/null"
|
|
70
|
+
if [ -n "$DEPLOY_URL" ]; then
|
|
71
|
+
check "POST /api/contact returns 200 or 400" "curl -s -X POST '$DEPLOY_URL/api/contact' -H 'Content-Type: application/json' -d '{\"name\":\"verify\",\"email\":\"verify@example.com\",\"message\":\"verify\"}' -o /dev/null -w '%{http_code}' | grep -E '^(200|400)$'"
|
|
72
|
+
fi
|
|
73
|
+
;;
|
|
74
|
+
booking)
|
|
75
|
+
echo ""
|
|
76
|
+
echo "--- booking ---"
|
|
77
|
+
check "booking API route exists" "find src/app/api/booking -type f 2>/dev/null | head -1 | grep -q ."
|
|
78
|
+
check "DATABASE_URL referenced in code or env" "grep -r 'DATABASE_URL' src/ .env.example 2>/dev/null | head -1"
|
|
79
|
+
;;
|
|
80
|
+
saas-dashboard)
|
|
81
|
+
echo ""
|
|
82
|
+
echo "--- saas-dashboard ---"
|
|
83
|
+
check "auth middleware exists" "[ -f src/middleware.ts ] || [ -f src/middleware.js ] || [ -f middleware.ts ]"
|
|
84
|
+
check "/login or /signup page exists" "find src/app -type d \\( -name 'login' -o -name 'signup' -o -name 'auth' \\) 2>/dev/null | head -1 | grep -q ."
|
|
85
|
+
check "/dashboard page exists" "find src/app -type d -name 'dashboard' 2>/dev/null | head -1 | grep -q ."
|
|
86
|
+
if [ -n "$DEPLOY_URL" ]; then
|
|
87
|
+
check "/dashboard redirects unauthenticated" "curl -s -o /dev/null -w '%{http_code}' '$DEPLOY_URL/dashboard' | grep -E '^(301|302|307|401)$'"
|
|
88
|
+
fi
|
|
89
|
+
;;
|
|
90
|
+
blog)
|
|
91
|
+
echo ""
|
|
92
|
+
echo "--- blog ---"
|
|
93
|
+
check "content/posts/ exists" "[ -d content/posts ] || [ -d src/content/posts ]"
|
|
94
|
+
check "mdx package installed" "grep -q '@next/mdx\\|next-mdx-remote\\|@mdx-js' package.json 2>/dev/null"
|
|
95
|
+
if [ -n "$DEPLOY_URL" ]; then
|
|
96
|
+
check "/rss.xml returns 200" "curl -sI '$DEPLOY_URL/rss.xml' | head -1 | grep -q 200"
|
|
97
|
+
check "/sitemap.xml returns 200" "curl -sI '$DEPLOY_URL/sitemap.xml' | head -1 | grep -q 200"
|
|
98
|
+
fi
|
|
99
|
+
;;
|
|
100
|
+
marketing|*)
|
|
101
|
+
echo ""
|
|
102
|
+
echo "--- marketing baseline ---"
|
|
103
|
+
if [ -n "$DEPLOY_URL" ]; then
|
|
104
|
+
check "homepage has hero text" "curl -s '$DEPLOY_URL' | grep -iE '<h1' | head -1 | grep -q ."
|
|
105
|
+
fi
|
|
106
|
+
;;
|
|
107
|
+
esac
|
|
108
|
+
|
|
109
|
+
# === Visual verification reminder ===
|
|
110
|
+
echo ""
|
|
111
|
+
echo "--- visual (per CLAUDE.md Part 19) ---"
|
|
112
|
+
if [ -n "$DEPLOY_URL" ] && [ -x "scripts/screenshot.sh" ]; then
|
|
113
|
+
scripts/screenshot.sh "$DEPLOY_URL" /tmp/verify-screenshot.png 420,900 > /dev/null && \
|
|
114
|
+
echo " Screenshot: /tmp/verify-screenshot.png (READ it to complete Part 19 verification)"
|
|
115
|
+
else
|
|
116
|
+
echo " Skipped (no deploy URL or no scripts/screenshot.sh)"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# === Summary ===
|
|
120
|
+
echo ""
|
|
121
|
+
echo "=========================================="
|
|
122
|
+
echo " Verify summary: PASS=$PASS FAIL=$FAIL"
|
|
123
|
+
echo "=========================================="
|
|
124
|
+
|
|
125
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
exit 0
|