Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Optional override for analysis + scaffold (default: Claude Sonnet 4.5 snapshot)
# ANTHROPIC_ANALYSIS_MODEL=claude-sonnet-4-5-20250929

# Stripe live billing
# Prefer a restricted live key (rk_live_...) with Checkout, Customer, Subscription,
# Billing Portal, and webhook read permissions.
STRIPE_LIVE_SECRET_KEY=rk_live_...
STRIPE_LIVE_WEBHOOK_SECRET=whsec_...
STRIPE_LIVE_PRO_PRICE_ID=price_...
STRIPE_LIVE_SCALE_PRICE_ID=price_...
4 changes: 2 additions & 2 deletions app/api/analyses/[id]/run/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
incrementAnalysisUsage,
} from '@/lib/queries'
import { getAnthropicModel } from '@/lib/anthropic-model'
import { PLANS } from '@/lib/stripe'
import { isPaidPlan, PLANS } from '@/lib/stripe'

// Schema for AI-generated app blueprints
const complexityEnum = z.preprocess((val) => {
Expand Down Expand Up @@ -162,7 +162,7 @@ export async function POST(
if (!sub) {
sub = await upsertSubscription({ github_id: user.github_id }).catch(() => null)
}
if (sub && sub.plan !== 'pro') {
if (sub && !isPaidPlan(sub.plan)) {
const limit = PLANS.free.analyses_per_month
if (sub.analyses_used_this_month >= limit) {
send({ error: `You've reached your free plan limit of ${limit} analyses per month. Upgrade to Pro for unlimited analyses.`, status: 'failed' })
Expand Down
25 changes: 21 additions & 4 deletions app/api/app-idea-chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface AppIdeaSuggestion {
suggestedStack: string[]
monetizationAngle: string
whyNow: string
reusePlan?: string
sourceFiles?: string[]
filesToCreate?: string[]
}

export interface AppIdeaChatResponse {
Expand Down Expand Up @@ -87,13 +90,22 @@ export async function POST(request: NextRequest) {
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([t]) => t)
const reusableFiles = allFiles
.sort((a, b) => b.reusability_score - a.reusability_score)
.slice(0, 16)
.map((file) => {
const repo = repositories.find((r) => r.id === file.repository_id)
return `${repo?.full_name ?? 'repo'}/${file.path}${file.purpose ? ` - ${file.purpose}` : ''}`
})

codebaseContext = `
## Developer's codebase context
Repositories: ${repositories.map((r) => r.name).join(', ')}
Top technologies: ${topTech.join(', ')}
Total files: ${allFiles.length}
Existing blueprints: ${blueprints.slice(0, 5).map((b) => b.name).join(', ') || 'none yet'}
Reusable source files:
${reusableFiles.length > 0 ? reusableFiles.map((file) => `- ${file}`).join('\n') : '- No analyzed file summaries yet'}
`
}
} catch {
Expand All @@ -107,13 +119,15 @@ Existing blueprints: ${blueprints.slice(0, 5).map((b) => b.name).join(', ') || '
content: m.content,
}))

const systemPrompt = `You are an expert product strategist and startup advisor helping developers discover what apps to build. You're having a friendly, concise conversation to help them find the perfect project idea.
const systemPrompt = `You are RepoFuse's VibeCoding app assembler. Help developers describe what they want to build, then turn their connected GitHub/GitLab repository knowledge into buildable app plans that reuse as much existing code, file structure, and patterns as possible.

${codebaseContext}

When responding:
- Keep your reply conversational and under 100 words
- Suggest 2-4 concrete project ideas tailored to their request${codebaseContext ? ' and their codebase' : ''}
- Suggest 2-4 concrete app builds tailored to their request${codebaseContext ? ' and their codebase' : ''}
- For each suggestion, explain how RepoFuse should stitch together existing files/patterns and which new files are needed
- Prefer specific source file paths from the codebase context when available
- Ask a relevant follow-up question to refine suggestions
- Be enthusiastic and actionable

Expand All @@ -127,10 +141,13 @@ Always respond with valid JSON only (no markdown fences):
"description": "2-3 sentences",
"type": "SaaS | CLI Tool | API | Dashboard | etc",
"difficulty": "easy | medium | hard",
"estimatedEffort": "e.g. 1–2 weeks",
"estimatedEffort": "e.g. Small MVP | Medium build | Larger build",
"suggestedStack": ["tech1", "tech2"],
"monetizationAngle": "How to charge",
"whyNow": "Why this is timely"
"whyNow": "Why this is timely",
"reusePlan": "How RepoFuse should combine existing repo code and patterns",
"sourceFiles": ["repo/path/to/reuse.ts"],
"filesToCreate": ["app/new-feature/page.tsx"]
}
],
"followUpQuestions": ["Question 1?", "Question 2?"]
Expand Down
4 changes: 2 additions & 2 deletions app/api/repositories/import/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { getCurrentAccessToken, getCurrentUser } from '@/lib/auth'
import { listGitHubRepositories } from '@/lib/github'
import { createRepository, getAllRepositories, getSubscriptionByGithubId, upsertSubscription } from '@/lib/queries'
import { PLANS } from '@/lib/stripe'
import { isPaidPlan, PLANS } from '@/lib/stripe'

export async function POST(request: NextRequest) {
try {
Expand All @@ -25,7 +25,7 @@ export async function POST(request: NextRequest) {
if (!sub) {
sub = await upsertSubscription({ github_id: user.github_id }).catch(() => null)
}
if (sub && sub.plan !== 'pro') {
if (sub && !isPaidPlan(sub.plan)) {
repoLimit = PLANS.free.repos_limit
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/api/repositories/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { getAllRepositories, createRepository, getSubscriptionByGithubId, upsertSubscription } from '@/lib/queries'
import { getCurrentUser } from '@/lib/auth'
import { PLANS } from '@/lib/stripe'
import { isPaidPlan, PLANS } from '@/lib/stripe'

export async function GET() {
try {
Expand All @@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
if (!sub) {
sub = await upsertSubscription({ github_id: user.github_id }).catch(() => null)
}
if (sub && sub.plan !== 'pro') {
if (sub && !isPaidPlan(sub.plan)) {
const repos = await getAllRepositories()
if (repos.length >= PLANS.free.repos_limit) {
return NextResponse.json(
Expand Down
2 changes: 1 addition & 1 deletion app/api/setup/init-db/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async function run() {
github_id BIGINT NOT NULL UNIQUE,
stripe_customer_id VARCHAR(255) UNIQUE,
stripe_subscription_id VARCHAR(255) UNIQUE,
plan VARCHAR(50) DEFAULT 'free' CHECK (plan IN ('free', 'pro')),
plan VARCHAR(50) DEFAULT 'free' CHECK (plan IN ('free', 'byok', 'pro', 'scale')),
status VARCHAR(50) DEFAULT 'active' CHECK (status IN ('active', 'past_due', 'canceled', 'trialing')),
current_period_end TIMESTAMP WITH TIME ZONE,
analyses_used_this_month INTEGER DEFAULT 0,
Expand Down
3 changes: 2 additions & 1 deletion app/api/stripe/checkout-redirect/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ export async function GET(request: NextRequest) {
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${appUrl}/dashboard?upgraded=true`,
cancel_url: `${appUrl}/pricing?cancelled=true`,
metadata: { github_id: String(user.github_id), plan: 'pro' },
subscription_data: {
trial_period_days: 14, // Launch offer: 14 days free
metadata: { github_id: String(user.github_id) },
metadata: { github_id: String(user.github_id), plan: 'pro' },
},
})

Expand Down
1 change: 1 addition & 0 deletions app/api/stripe/checkout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export async function POST(request: NextRequest) {
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${appUrl}/dashboard?upgraded=true`,
cancel_url: `${appUrl}/pricing`,
metadata: { github_id: String(user.github_id), plan },
subscription_data: {
metadata: { github_id: String(user.github_id), plan },
},
Expand Down
Loading
Loading