diff --git a/.env.example b/.env.example
index 41d5a71..309eb46 100644
--- a/.env.example
+++ b/.env.example
@@ -3,17 +3,19 @@ DATABASE_URL=postgresql://user:password@host/dbname?sslmode=require
# GitHub OAuth App
# Create at: https://github.com/settings/developers
-GITHUB_CLIENT_ID=0v23li58m3t8TIbfIr8A
+GITHUB_CLIENT_ID=your-github-client-id
# Optional fallback for older deployments. Client-side/public only, not a secret.
-NEXT_PUBLIC_GITHUB_CLIENT_ID=Ov231iS8m3t8TIbfIr8A
-GITHUB_CLIENT_SECRET=8643494973801aaade8e7cd77ee89d3700
-2ce897
+NEXT_PUBLIC_GITHUB_CLIENT_ID=your-github-client-id
+GITHUB_CLIENT_SECRET=your-github-client-secret
# Public URL of your app (used for OAuth callback redirect)
-NEXT_PUBLIC_APP_URL=https://repo-app-architect.vercel.app
+NEXT_PUBLIC_APP_URL=your-app-url
-# OpenAI API Key (used by Vercel AI SDK for analysis)
-OPENAI_API_KEY=sk-...
-
-# Anthropic API Key (used for scaffold generation)
+# Anthropic API Key (used for analysis and scaffold generation)
ANTHROPIC_API_KEY=sk-ant-...
+
+# Optional Claude model override for analysis
+ANTHROPIC_ANALYSIS_MODEL=claude-3-5-sonnet-20241022
+
+# Stripe Checkout
+STRIPE_SECRET_KEY=sk_test_...
diff --git a/app/api/analyses/[id]/route.ts b/app/api/analyses/[id]/route.ts
index 0cfe7e4..8f62e49 100644
--- a/app/api/analyses/[id]/route.ts
+++ b/app/api/analyses/[id]/route.ts
@@ -1,36 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
import { getAnalysisById, getBlueprintsByAnalysis, getRepositoriesForAnalysis } from '@/lib/queries'
-export async function GET(
- _request: NextRequest,
- { params }: { params: Promise<{ id: string }> },
-) {
- try {
- const { id } = await params
- const analysis = await getAnalysisById(id)
- if (!analysis) {
- return NextResponse.json({ error: 'Analysis not found' }, { status: 404 })
- }
-
- const [repositories, blueprints] = await Promise.all([
- getRepositoriesForAnalysis(id),
- getBlueprintsByAnalysis(id),
- ])
-
- return NextResponse.json({
- ...analysis,
- repositories,
- blueprints,
- apps: blueprints,
- })
- } catch (error) {
- console.error('Error fetching analysis details:', error)
- return NextResponse.json({ error: 'Failed to fetch analysis details' }, { status: 500 })
- }
-}
-import { NextRequest, NextResponse } from 'next/server'
-import { getAnalysisById, getBlueprintsByAnalysis, getRepositoriesForAnalysis } from '@/lib/queries'
-
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
diff --git a/app/api/analyses/[id]/run/route.ts b/app/api/analyses/[id]/run/route.ts
index 6db1417..d7e6763 100644
--- a/app/api/analyses/[id]/run/route.ts
+++ b/app/api/analyses/[id]/run/route.ts
@@ -1,8 +1,11 @@
import { NextRequest } from 'next/server'
-import { generateText, Output } from 'ai'
-import { z } from 'zod'
+import Anthropic from '@anthropic-ai/sdk'
import { getCurrentAccessToken } from '@/lib/auth'
import { getGitHubRepositoryTree } from '@/lib/github'
+import {
+ getAnthropicAnalysisModel,
+ parseAnalysisBlueprintOutput,
+} from '@/lib/analysis-blueprints'
import {
getAnalysisById,
getRepositoriesForAnalysis,
@@ -13,29 +16,6 @@ import {
getBlueprintsByAnalysis
} from '@/lib/queries'
-// Schema for AI-generated app blueprints
-const BlueprintSchema = z.object({
- name: z.string(),
- description: z.string(),
- app_type: z.string(),
- complexity: z.enum(['simple', 'moderate', 'complex']),
- reuse_percentage: z.number().min(0).max(100),
- existing_files: z.array(z.object({
- path: z.string(),
- purpose: z.string(),
- })),
- missing_files: z.array(z.object({
- name: z.string(),
- purpose: z.string(),
- })),
- technologies: z.array(z.string()),
- explanation: z.string(),
-})
-
-const AnalysisOutputSchema = z.object({
- blueprints: z.array(BlueprintSchema),
-})
-
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
@@ -57,8 +37,8 @@ export async function POST(
controller.close()
return
}
- if (!process.env.OPENAI_API_KEY) {
- send({ error: 'AI analysis is not configured. Missing OPENAI_API_KEY.' })
+ if (!process.env.ANTHROPIC_API_KEY) {
+ send({ error: 'AI analysis is not configured. Missing ANTHROPIC_API_KEY.' })
controller.close()
return
}
@@ -132,11 +112,18 @@ export async function POST(
// Build file summary for AI
const fileSummary = allFiles.map(f => `- ${f.repo}: ${f.path}`).join('\n')
+ const anthropic = new Anthropic({
+ apiKey: process.env.ANTHROPIC_API_KEY,
+ })
+
// Use AI to analyze and discover app blueprints
- const { output } = await generateText({
- model: 'openai/gpt-4o-mini',
- output: Output.object({ schema: AnalysisOutputSchema }),
- prompt: `You are an expert software architect. Analyze these files from GitHub repositories and discover what applications can be built by combining and reusing the existing code.
+ const response = await anthropic.messages.create({
+ model: getAnthropicAnalysisModel(),
+ max_tokens: 4000,
+ temperature: 0.2,
+ messages: [{
+ role: 'user',
+ content: `You are an expert software architect. Analyze these files from GitHub repositories and discover what applications can be built by combining and reusing the existing code.
REPOSITORIES AND FILES:
${fileSummary}
@@ -155,8 +142,31 @@ For each app blueprint:
- List technologies detected
- Provide a brief explanation of why this app is possible
+Return only valid JSON with this shape:
+{
+ "blueprints": [
+ {
+ "name": "string",
+ "description": "string",
+ "app_type": "string",
+ "complexity": "simple" | "moderate" | "complex",
+ "reuse_percentage": 0-100,
+ "existing_files": [{ "path": "repo/path.ext", "purpose": "string" }],
+ "missing_files": [{ "name": "path-or-feature", "purpose": "string" }],
+ "technologies": ["string"],
+ "explanation": "string"
+ }
+ ]
+}
+
Focus on practical, buildable applications based on the actual code patterns you see.`,
+ }],
})
+ const text = response.content
+ .filter((block) => block.type === 'text')
+ .map((block) => block.text)
+ .join('\n')
+ const output = parseAnalysisBlueprintOutput(text)
send({ status: 'analyzing', progress: 80 })
@@ -190,10 +200,11 @@ Focus on practical, buildable applications based on the actual code patterns you
} catch (error) {
console.error('Analysis error:', error)
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
await updateAnalysisStatus(id, 'failed', {
- error_message: error instanceof Error ? error.message : 'Unknown error'
+ error_message: errorMessage
})
- send({ status: 'failed', error: 'Analysis failed' })
+ send({ status: 'failed', error: errorMessage })
controller.close()
}
},
diff --git a/app/api/stripe/checkout/route.ts b/app/api/stripe/checkout/route.ts
new file mode 100644
index 0000000..a215b06
--- /dev/null
+++ b/app/api/stripe/checkout/route.ts
@@ -0,0 +1,63 @@
+import { NextRequest, NextResponse } from 'next/server'
+import Stripe from 'stripe'
+
+import { getPlanById } from '@/lib/billing'
+
+const stripeSecretKey = process.env.STRIPE_SECRET_KEY
+const stripe = stripeSecretKey ? new Stripe(stripeSecretKey) : null
+
+function getBaseUrl(request: NextRequest) {
+ return process.env.NEXT_PUBLIC_APP_URL || request.nextUrl.origin
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const { planId } = (await request.json()) as { planId?: string }
+ const plan = getPlanById(planId)
+
+ if (!plan) {
+ return NextResponse.json({ error: 'Unknown billing plan.' }, { status: 400 })
+ }
+
+ if (!stripe || !process.env.STRIPE_SECRET_KEY) {
+ return NextResponse.json(
+ { error: 'Stripe is not configured. Set STRIPE_SECRET_KEY to enable Checkout.' },
+ { status: 503 },
+ )
+ }
+
+ const baseUrl = getBaseUrl(request)
+ const session = await stripe.checkout.sessions.create({
+ mode: 'subscription',
+ billing_address_collection: 'auto',
+ allow_promotion_codes: true,
+ line_items: [
+ {
+ price_data: {
+ currency: plan.currency,
+ recurring: { interval: plan.interval },
+ unit_amount: plan.unitAmount,
+ product_data: {
+ name: plan.name,
+ description: plan.description,
+ metadata: {
+ plan_id: plan.id,
+ },
+ },
+ },
+ quantity: 1,
+ },
+ ],
+ metadata: {
+ plan_id: plan.id,
+ },
+ success_url: `${baseUrl}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
+ cancel_url: `${baseUrl}/billing/cancel`,
+ })
+
+ return NextResponse.json({ url: session.url })
+ } catch (error) {
+ console.error('Error creating Stripe Checkout session:', error)
+ return NextResponse.json({ error: 'Failed to create Checkout session.' }, { status: 500 })
+ }
+}
diff --git a/app/billing/cancel/page.tsx b/app/billing/cancel/page.tsx
new file mode 100644
index 0000000..30ddba6
--- /dev/null
+++ b/app/billing/cancel/page.tsx
@@ -0,0 +1,43 @@
+import Link from 'next/link'
+import { ArrowLeft, CreditCard, RefreshCcw } from 'lucide-react'
+
+import { AppLogo } from '@/components/app-logo'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+
+export default function BillingCancelPage() {
+ return (
+
+
+
+
+
+
+
+
+ Checkout canceled
+
+
+
+ No payment was completed. You can return to pricing and start a new secure Stripe Checkout session whenever you are ready.
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/billing/success/page.tsx b/app/billing/success/page.tsx
new file mode 100644
index 0000000..2c11495
--- /dev/null
+++ b/app/billing/success/page.tsx
@@ -0,0 +1,44 @@
+import Link from 'next/link'
+import { CheckCircle2, ArrowRight } from 'lucide-react'
+
+import { Button } from '@/components/ui/button'
+import { Card } from '@/components/ui/card'
+
+export default async function BillingSuccessPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ session_id?: string }>
+}) {
+ const { session_id } = await searchParams
+
+ return (
+
+
+
+
+
+
You are on the Pro launch path.
+
+ Stripe confirmed the checkout redirect. Once webhooks are connected, this is where CodeVault can
+ activate billing entitlements and show plan details.
+
+ {session_id ? (
+
+ Checkout session: {session_id}
+
+ ) : null}
+
+
+
+
+
+
+ )
+}
diff --git a/app/page.tsx b/app/page.tsx
index fde5c59..27849ec 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,10 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
-import { Github, Sparkles, Code2, Layers, ArrowRight, AlertCircle } from 'lucide-react'
+import { Github, Sparkles, Code2, ArrowRight, AlertCircle, ShieldCheck, Workflow, FileJson2, CheckCircle2 } from 'lucide-react'
+import { AppLogo } from '@/components/app-logo'
+import { ThemeToggle } from '@/components/theme-toggle'
+import { CheckoutButton } from '@/components/checkout-button'
+import { CODEVAULT_PRO_PLAN } from '@/lib/billing'
const ERROR_MESSAGES: Record = {
auth_required: 'You must sign in to access the dashboard.',
@@ -27,20 +31,17 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
{/* Header */}