diff --git a/data/migration-guides.json b/data/migration-guides.json new file mode 100644 index 00000000..04ea2ce1 --- /dev/null +++ b/data/migration-guides.json @@ -0,0 +1,172 @@ +{ + "guides": [ + { + "id": "langchain-to-foragents", + "fromPlatform": "langchain", + "toPlatform": "foragents", + "title": "LangChain → forAgents.dev", + "description": "Move from chain-heavy orchestration to modular skills, reusable prompts, and persistent memory.", + "difficulty": "medium", + "estimatedTime": "2-3 hours", + "completions": 134, + "steps": [ + { + "title": "Audit your current chains", + "content": "List your active chains, tools, and memory modules.\n\n- Document each chain purpose\n- Note external APIs and secrets\n- Capture expected outputs for test parity" + }, + { + "title": "Map chains to skills", + "content": "Replace tightly coupled chain logic with dedicated skills.\n\n```bash\nmkdir -p skills/research skills/summarize\n```\n\nCreate a `SKILL.md` for each capability so behavior is explicit and versionable." + }, + { + "title": "Port memory strategy", + "content": "Use file-based memory conventions:\n\n- `memory/YYYY-MM-DD.md` for daily context\n- `MEMORY.md` for durable facts\n\nThis makes context portable across hosts and sessions." + }, + { + "title": "Validate with side-by-side prompts", + "content": "Run 5-10 representative prompts in both systems. Compare correctness, latency, and token usage before cutover." + } + ] + }, + { + "id": "crewai-to-foragents", + "fromPlatform": "crewai", + "toPlatform": "foragents", + "title": "CrewAI → forAgents.dev", + "description": "Convert role-based crews into lane-based workflows with shared backlog and deterministic handoffs.", + "difficulty": "easy", + "estimatedTime": "60-90 minutes", + "completions": 89, + "steps": [ + { + "title": "Define lane ownership", + "content": "Translate CrewAI roles into lanes (`research`, `build`, `review`) and assign single owners to avoid duplicate work." + }, + { + "title": "Move tasks into BACKLOG.md", + "content": "Represent each task with status and output location.\n\n```md\n### Improve onboarding flow\n- lane: build\n- status: ready\n- output: docs/onboarding.md\n```" + }, + { + "title": "Add heartbeat instructions", + "content": "Set heartbeat rules so agents can self-pull ready work and report blockers without central orchestration." + }, + { + "title": "Run a pilot sprint", + "content": "Use one real backlog item and verify handoff quality across at least two lanes before full migration." + } + ] + }, + { + "id": "autogen-to-foragents", + "fromPlatform": "autogen", + "toPlatform": "foragents", + "title": "AutoGen → forAgents.dev", + "description": "Simplify multi-agent chat loops into actionable artifacts, lane contracts, and reusable skills.", + "difficulty": "medium", + "estimatedTime": "2 hours", + "completions": 57, + "steps": [ + { + "title": "Capture your current agent graph", + "content": "Document which agents talk to whom, and where decisions are currently made in free-form chat loops." + }, + { + "title": "Replace chat loops with artifacts", + "content": "Require each stage to write a concrete output (spec, diff, test report) instead of unbounded conversation." + }, + { + "title": "Extract stable behaviors into skills", + "content": "Any repeated behavior should become a `SKILL.md` plus examples. This reduces drift and improves onboarding." + }, + { + "title": "Enforce acceptance checks", + "content": "Before handoff, validate against explicit criteria (tests pass, docs updated, no lint regressions)." + } + ] + }, + { + "id": "openai-agents-to-foragents", + "fromPlatform": "openai-agents", + "toPlatform": "foragents", + "title": "OpenAI Agents SDK → forAgents.dev", + "description": "Retain tool-calling power while moving execution contracts and memory into portable workspace files.", + "difficulty": "hard", + "estimatedTime": "3-4 hours", + "completions": 31, + "steps": [ + { + "title": "Inventory tools and guards", + "content": "List all tools, guardrails, and expected input/output schemas from your current SDK setup." + }, + { + "title": "Recreate tool contracts in skills", + "content": "Define each tool's capability, constraints, and failure handling in skill docs so behavior is transparent." + }, + { + "title": "Externalize runtime context", + "content": "Move persistent instructions into `AGENTS.md`, `SOUL.md`, and memory files to avoid hidden runtime coupling." + }, + { + "title": "Test fallback paths", + "content": "Validate retries, partial failures, and rate limits. Add runbooks in `memory/procedures/` for recurring incidents." + } + ] + }, + { + "id": "custom-stack-to-foragents", + "fromPlatform": "custom", + "toPlatform": "foragents", + "title": "Custom Stack → forAgents.dev", + "description": "Migrate bespoke scripts and prompts into a maintainable, shareable skill-and-memory architecture.", + "difficulty": "hard", + "estimatedTime": "4-6 hours", + "completions": 22, + "steps": [ + { + "title": "Snapshot current behavior", + "content": "Record baseline outputs for your top workflows so you can verify migration parity later." + }, + { + "title": "Decompose monolith logic", + "content": "Split script logic into capabilities: retrieval, transformation, reporting, and escalation." + }, + { + "title": "Build skill folders", + "content": "Create one folder per capability with a concise `SKILL.md`, examples, and required env vars." + }, + { + "title": "Introduce shared operational memory", + "content": "Track incidents, fixes, and lessons in memory files to reduce repeated debugging and onboarding time." + } + ] + }, + { + "id": "foragents-v1-to-v2", + "fromPlatform": "foragents-v1", + "toPlatform": "foragents", + "title": "forAgents v1 → v2", + "description": "Upgrade to v2 conventions with cleaner memory boundaries, better API integrations, and improved workflow clarity.", + "difficulty": "easy", + "estimatedTime": "30-45 minutes", + "completions": 203, + "steps": [ + { + "title": "Update workspace conventions", + "content": "Adopt v2 defaults for memory and heartbeat files; remove obsolete v1 task patterns." + }, + { + "title": "Refresh core skills", + "content": "Pull latest skill versions and verify local overrides still match your intent." + }, + { + "title": "Re-run lint and build", + "content": "Use project checks to catch outdated imports, stale APIs, and typing regressions before rollout." + }, + { + "title": "Document migration decisions", + "content": "Write what changed and why in `memory/YYYY-MM-DD.md` so future contributors can reason about the upgrade." + } + ] + } + ] +} diff --git a/src/app/api/migrate/route.ts b/src/app/api/migrate/route.ts new file mode 100644 index 00000000..3d87ae85 --- /dev/null +++ b/src/app/api/migrate/route.ts @@ -0,0 +1,114 @@ +import { NextRequest, NextResponse } from "next/server" +import { promises as fs } from "node:fs" +import path from "node:path" + +type Difficulty = "easy" | "medium" | "hard" + +interface MigrationStep { + title: string + content: string +} + +interface MigrationGuide { + id: string + fromPlatform: string + toPlatform: "foragents" + title: string + description: string + steps: MigrationStep[] + difficulty: Difficulty + estimatedTime: string + completions: number +} + +interface MigrationGuideStore { + guides: MigrationGuide[] +} + +const DATA_PATH = path.join(process.cwd(), "data", "migration-guides.json") + +async function readGuideStore() { + const raw = await fs.readFile(DATA_PATH, "utf8") + return JSON.parse(raw) as MigrationGuideStore +} + +async function writeGuideStore(store: MigrationGuideStore) { + await fs.writeFile(DATA_PATH, JSON.stringify(store, null, 2) + "\n", "utf8") +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + + const difficulty = searchParams.get("difficulty")?.trim().toLowerCase() + const search = searchParams.get("search")?.trim().toLowerCase() + const fromPlatform = searchParams.get("fromPlatform")?.trim().toLowerCase() + + const validDifficulty = ["easy", "medium", "hard"] as const + const hasDifficultyFilter = Boolean( + difficulty && validDifficulty.includes(difficulty as Difficulty) + ) + + const store = await readGuideStore() + + const guides = store.guides.filter((guide) => { + const matchesDifficulty = hasDifficultyFilter + ? guide.difficulty === difficulty + : true + + const matchesFromPlatform = fromPlatform + ? guide.fromPlatform.toLowerCase() === fromPlatform + : true + + const matchesSearch = search + ? `${guide.title} ${guide.description}` + .toLowerCase() + .includes(search) + : true + + return matchesDifficulty && matchesFromPlatform && matchesSearch + }) + + return NextResponse.json({ guides, count: guides.length }) + } catch (error) { + console.error("Failed to fetch migration guides", error) + return NextResponse.json( + { error: "Failed to fetch migration guides" }, + { status: 500 } + ) + } +} + +export async function POST(request: NextRequest) { + try { + const body = (await request.json()) as { guideId?: unknown } + const guideId = typeof body.guideId === "string" ? body.guideId.trim() : "" + + if (!guideId) { + return NextResponse.json({ error: "guideId is required" }, { status: 400 }) + } + + const store = await readGuideStore() + const guideIndex = store.guides.findIndex((guide) => guide.id === guideId) + + if (guideIndex === -1) { + return NextResponse.json({ error: "Guide not found" }, { status: 404 }) + } + + const updatedGuide = { + ...store.guides[guideIndex], + completions: store.guides[guideIndex].completions + 1, + } + + store.guides[guideIndex] = updatedGuide + await writeGuideStore(store) + + return NextResponse.json({ guide: updatedGuide }) + } catch (error) { + console.error("Failed to mark migration guide as complete", error) + return NextResponse.json( + { error: "Failed to mark guide complete" }, + { status: 500 } + ) + } +} diff --git a/src/app/migrate/page.tsx b/src/app/migrate/page.tsx index 70a5718b..157de6a6 100644 --- a/src/app/migrate/page.tsx +++ b/src/app/migrate/page.tsx @@ -1,344 +1,337 @@ +/* eslint-disable react/no-unescaped-entities */ "use client" -import { useState, useEffect } from "react" -import Link from "next/link" -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" -import { Checkbox } from "@/components/ui/checkbox" -import migrationData from "@/data/migration-guides.json" +import { useEffect, useMemo, useState } from "react" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" -interface CodeExample { - before: string - after: string +type Difficulty = "easy" | "medium" | "hard" + +interface MigrationStep { + title: string + content: string } -interface Step { +interface MigrationGuide { + id: string + fromPlatform: string + toPlatform: "foragents" title: string description: string - code?: CodeExample + steps: MigrationStep[] + difficulty: Difficulty + estimatedTime: string + completions: number } -interface Pitfall { - issue: string - solution: string +const difficultyLabelClass: Record = { + easy: "bg-emerald-500/20 text-emerald-300 border-emerald-500/40", + medium: "bg-amber-500/20 text-amber-300 border-amber-500/40", + hard: "bg-rose-500/20 text-rose-300 border-rose-500/40", } -interface Guide { - id: string - title: string - estimatedTime: string - overview: string - prerequisites: string[] - steps: Step[] - commonPitfalls: Pitfall[] +function platformLabel(platform: string) { + return platform + .split("-") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" ") } export default function MigratePage() { - const [activeGuide, setActiveGuide] = useState("langchain") - // Load progress from localStorage with lazy initializer - const [checkedSteps, setCheckedSteps] = useState>(() => { - if (typeof window === "undefined") return {} - const savedProgress = localStorage.getItem("migration-progress") - if (savedProgress) { + const [guides, setGuides] = useState([]) + const [allGuides, setAllGuides] = useState([]) + const [selectedGuideId, setSelectedGuideId] = useState("") + const [difficulty, setDifficulty] = useState("all") + const [fromPlatform, setFromPlatform] = useState("all") + const [search, setSearch] = useState("") + const [loading, setLoading] = useState(true) + const [markingCompleteId, setMarkingCompleteId] = useState(null) + const [error, setError] = useState(null) + + const platformOptions = useMemo(() => { + return Array.from(new Set(allGuides.map((guide) => guide.fromPlatform))).sort() + }, [allGuides]) + + useEffect(() => { + const fetchAllGuides = async () => { try { - return JSON.parse(savedProgress) - } catch (e) { - console.error("Failed to parse saved progress", e) - return {} + const response = await fetch("/api/migrate", { cache: "no-store" }) + if (!response.ok) { + throw new Error("Failed to load migration guides") + } + + const payload = (await response.json()) as { guides: MigrationGuide[] } + setAllGuides(payload.guides) + } catch (fetchError) { + console.error(fetchError) } } - return {} - }) - // Save progress to localStorage whenever it changes + fetchAllGuides() + }, []) + useEffect(() => { - localStorage.setItem("migration-progress", JSON.stringify(checkedSteps)) - }, [checkedSteps]) - - const toggleStep = (guideId: string, stepIndex: number) => { - setCheckedSteps((prev) => { - const guideSteps = prev[guideId] || [] - const newSteps = [...guideSteps] - newSteps[stepIndex] = !newSteps[stepIndex] - return { - ...prev, - [guideId]: newSteps, + const fetchFilteredGuides = async () => { + try { + setLoading(true) + setError(null) + + const params = new URLSearchParams() + if (difficulty !== "all") params.set("difficulty", difficulty) + if (fromPlatform !== "all") params.set("fromPlatform", fromPlatform) + if (search.trim()) params.set("search", search.trim()) + + const query = params.toString() + const response = await fetch(`/api/migrate${query ? `?${query}` : ""}`, { + cache: "no-store", + }) + + if (!response.ok) { + throw new Error("Failed to load migration guides") + } + + const payload = (await response.json()) as { guides: MigrationGuide[] } + setGuides(payload.guides) + } catch (fetchError) { + console.error(fetchError) + setError("Couldn't load migration guides right now. Please try again.") + } finally { + setLoading(false) } - }) - } + } - const getProgress = (guideId: string, totalSteps: number) => { - const steps = checkedSteps[guideId] || [] - const completed = steps.filter(Boolean).length - return { completed, total: totalSteps, percentage: Math.round((completed / totalSteps) * 100) || 0 } - } + fetchFilteredGuides() + }, [difficulty, fromPlatform, search]) - const guides: Guide[] = migrationData.guides + useEffect(() => { + if (!guides.length) { + setSelectedGuideId("") + return + } - return ( -
- {/* Hero */} -
-
-
-
+ const hasSelectedGuide = guides.some((guide) => guide.id === selectedGuideId) + if (!hasSelectedGuide) { + setSelectedGuideId(guides[0].id) + } + }, [guides, selectedGuideId]) + + const selectedGuide = guides.find((guide) => guide.id === selectedGuideId) + + const markComplete = async (guideId: string) => { + try { + setMarkingCompleteId(guideId) -
+ const response = await fetch("/api/migrate", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ guideId }), + }) + + if (!response.ok) { + throw new Error("Failed to mark guide complete") + } + + const payload = (await response.json()) as { guide: MigrationGuide } + + setGuides((prev) => + prev.map((guide) => (guide.id === guideId ? payload.guide : guide)) + ) + setAllGuides((prev) => + prev.map((guide) => (guide.id === guideId ? payload.guide : guide)) + ) + } catch (markError) { + console.error(markError) + } finally { + setMarkingCompleteId(null) + } + } + + return ( +
+
+
🔄 - Interactive Migration Guide + Migration Guide
-

+

Migrate to forAgents.dev

-

- Step-by-step guides with progress tracking to help you migrate from LangChain, CrewAI, custom solutions, or upgrade from v1 to v2. +

+ Real migration data, persistent completion tracking, and step-by-step + instructions for moving from your current platform when you're ready.

-
- - {/* Migration Guides Tabs */} -
- - - {guides.map((guide) => ( - - {guide.title} - - ))} - - - {guides.map((guide) => { - const progress = getProgress(guide.id, guide.steps.length) - - return ( - - {/* Guide Header */} -
-
-
-

{guide.title}

-

{guide.overview}

-
-
-
Estimated Time
-
{guide.estimatedTime}
-
-
- - {/* Progress Bar */} -
-
- Progress - - {progress.completed} / {progress.total} steps ({progress.percentage}%) - -
-
-
-
-
-
- {/* Prerequisites */} -
-

📋 Prerequisites

-
-
    - {guide.prerequisites.map((prereq, index) => ( -
  • - - {prereq} -
  • - ))} -
-
-
+
+ setSearch(event.target.value)} + placeholder="Search migration guides" + className="md:col-span-1 bg-white/5 border-white/20" + /> - {/* Migration Steps */} -
-

🚀 Migration Steps

-
- {guide.steps.map((step, index) => { - const isChecked = checkedSteps[guide.id]?.[index] || false - - return ( -
- {/* Step Header */} -
- toggleStep(guide.id, index)} - className="mt-1" - /> -
-
- - Step {index + 1} - - {isChecked && ( - - Completed - - )} -
-

{step.title}

-

{step.description}

-
-
- - {/* Code Examples */} - {step.code && ( -
-
-
- - Before: -
-
-                                  {step.code.before}
-                                
-
-
-
- - After: -
-
-                                  {step.code.after}
-                                
-
-
- )} -
- ) - })} -
-
- - {/* Common Pitfalls */} -
-

⚠️ Common Pitfalls

-
- {guide.commonPitfalls.map((pitfall, index) => ( -
-
- ⚠️ -
-

{pitfall.issue}

-

- Solution:{" "} - {pitfall.solution} -

-
-
-
- ))} -
-
- - {/* Completion CTA */} - {progress.percentage === 100 && ( -
-
🎉
-

Migration Complete!

-

- Congratulations! You've completed the migration guide. -

- - Browse Skills → - -
- )} - - ) - })} - -
+ - {/* Why Migrate Section */} -
-

Why Migrate?

- -
-
-
🚀
-

Faster Development

-

- Skip the boilerplate. Our skills are pre-built, tested, and ready to install. What takes days in other frameworks takes minutes here. -

-
+ +
-
-
🔧
-

Less Maintenance

-

- Community-maintained skills mean updates are handled for you. No more wrestling with breaking changes or deprecated APIs. -

+ {loading && ( +
+ Loading migration guides...
+ )} -
-
📦
-

Better Ecosystem

-

- Access a growing library of skills, MCP servers, and agent templates. If you need it, someone's probably built it. -

+ {error && ( +
+ {error}
+ )} -
-
🤝
-

Framework Agnostic

-

- Skills work with any agent framework or custom setup. You're not locked into a specific architecture or vendor. -

+ {!loading && !error && guides.length === 0 && ( +
+ No migration guides matched your filters.
-
-
+ )} - {/* Need Help CTA */} -
-
-
- -
-
🤝
-

- Need Help Migrating? -

-

- Stuck on a migration step? Have questions about specific skills? Our community is here to help. -

-
- - Get Migration Support → - - - Browse Guides - + {!loading && guides.length > 0 && ( + <> +
+ {guides.map((guide) => ( + setSelectedGuideId(guide.id)} + > + +
+ + {guide.difficulty} + + + {platformLabel(guide.fromPlatform)} + +
+ {guide.title} + {guide.description} +
+ +

Estimated time: {guide.estimatedTime}

+

Completions: {guide.completions}

+
+ + + +
+ ))}
-
-
+ + {selectedGuide && ( +
+
+
+

{selectedGuide.title}

+

{selectedGuide.description}

+
+
+

From: {platformLabel(selectedGuide.fromPlatform)}

+

To: forAgents.dev

+

Completions: {selectedGuide.completions}

+
+
+ + + {selectedGuide.steps.map((step, index) => ( + + + + Step {index + 1}: {step.title} + + + +
+ {step.content} +
+
+
+ ))} +
+ +
+

+ Finished this guide? Mark it complete to increment the shared completion count. +

+ +
+
+ )} + + )}
)