diff --git a/README.md b/README.md index 5f44a28..2fa6935 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ For information on how to run each project, see the README in each directory. | [Building effective agents](/building-effective-agents) | 5 different patterns for building effective AI agents with Trigger.dev; [Prompt chaining](/building-effective-agents/src/trigger/trigger/translate-copy.ts), [Routing](/building-effective-agents/src/trigger/trigger/routing-questions.ts), [Parallelization](/building-effective-agents/src/trigger/trigger/parallel-llm-calls.ts), [Orchestrator-workers](/building-effective-agents/src/trigger/trigger/orchestrator-workers.ts) | | [Claude thinking chatbot](/claude-thinking-chatbot) | A chatbot that uses Claude's thinking capabilities to generate responses | | [Claude agent SDK](/claude-agent-sdk-trigger) | A simple example of how to use the [Claude Agent SDK](https://docs.claude.com/en/docs/agent-sdk/overview) with Trigger.dev | +| [Claude agent GitHub wiki](/claude-agent-github-wiki) | AI-powered repository analyzer that lets you ask questions about any public GitHub repository using Anthropic's [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview), with real-time streaming via [Trigger.dev Realtime](https://trigger.dev/docs/realtime/overview) | | [Deep research agent using the AI SDK](/vercel-ai-sdk-deep-research-agent/) | An intelligent deep research agent using the Vercel [AI SDK](https://sdk.vercel.ai/docs/introduction) and Trigger.dev | | [Monorepos](/monorepos) | Examples of using Trigger.dev in monorepo setups with [Turborepo](https://turbo.build/) and [Prisma](https://www.prisma.io/) | | [Next.js server actions](/nextjs-server-actions) | A [Next.js app](https://nextjs.org/) that triggers Trigger.dev tasks using Server Actions | diff --git a/claude-agent-github-wiki/.env.example b/claude-agent-github-wiki/.env.example new file mode 100644 index 0000000..c3eab0f --- /dev/null +++ b/claude-agent-github-wiki/.env.example @@ -0,0 +1,8 @@ +# Trigger.dev Configuration +# Get these from https://cloud.trigger.dev +TRIGGER_PROJECT_REF=your_project_ref +TRIGGER_SECRET_KEY=your_secret_key + +# Claude API Key +# Get from https://console.anthropic.com +ANTHROPIC_API_KEY=your_anthropic_api_key diff --git a/claude-agent-github-wiki/.eslintrc.json b/claude-agent-github-wiki/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/claude-agent-github-wiki/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/claude-agent-github-wiki/.gitignore b/claude-agent-github-wiki/.gitignore new file mode 100644 index 0000000..c3f40f4 --- /dev/null +++ b/claude-agent-github-wiki/.gitignore @@ -0,0 +1,62 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +CLAUDE.md + +postgres-data +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# next.js +.next/ +out/ +dist +packages/**/dist + +# Tailwind +apps/**/styles/tailwind.css +packages/**/styles/tailwind.css + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.docker +.docker/*.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo +.vercel +.cache +.env +.output +apps/**/public/build +.tests-container-id.txt +.sentryclirc +.buildt + +**/tmp/ +/test-results/ +/playwright-report/ +/playwright/.cache/ + +.cosine +.trigger +.tshy* +.yarn +*.tsbuildinfo +.claude \ No newline at end of file diff --git a/claude-agent-github-wiki/README.md b/claude-agent-github-wiki/README.md new file mode 100644 index 0000000..4a6c7a7 --- /dev/null +++ b/claude-agent-github-wiki/README.md @@ -0,0 +1,105 @@ +# GitHub repository analyzer with Claude Agent SDK + +AI-powered repository analyzer that lets you ask questions about any public GitHub repository. Uses Anthropic's Claude Agent SDK with agentic tools to explore codebases and provide detailed answers, with real-time streaming responses to the frontend via Trigger.dev. + +## Tech Stack + +- [**Next.js**](https://nextjs.org/) – React framework with App Router +- [**Claude Agent SDK**](https://platform.claude.com/docs/en/agent-sdk/overview) – Anthropic's SDK for building AI agents; provides an agentic loop with shell, file, and search tools +- [**Trigger.dev**](https://trigger.dev/) – runs the agent in a long-running background task with real-time streaming to the frontend + +## Features + +- **Ask anything about any public repo** – Architecture, security vulnerabilities, API endpoints, testing strategies, etc. +- **Claude Agent SDK exploration** – Claude explores the codebase and provide detailed answers +- **Cancel anytime** – Abort long-running Trigger.dev task with cleanup +- **Trigger.dev Realtime streaming** – Watch Claude's analysis stream in as it's generated +- **Progress tracking using Trigger.dev Realtime** – See clone status, analysis progress, and repo size + +## Setup & Running Locally + +1. **Clone the repository** + + ```bash + git clone + cd claude-agent-github-wiki + ``` + +2. **Install dependencies** + + ```bash + npm install + ``` + +3. **Copy environment variables and configure** + + ```bash + cp .env.example .env + ``` + + Fill in the required variables: + + - `TRIGGER_SECRET_KEY` – Get from [Trigger.dev dashboard](https://cloud.trigger.dev/) + - `TRIGGER_PROJECT_REF` – Your Trigger.dev project ref (starts with `proj_`) + - `ANTHROPIC_API_KEY` – Get from [Anthropic Console](https://console.anthropic.com/) + +4. **Start development servers** + + ```bash + # Terminal 1: Start Next.js dev server + npm run dev + + # Terminal 2: Start Trigger.dev CLI + npx trigger.dev@latest dev + ``` + + Open [http://localhost:3000](http://localhost:3000) + +## How It Works + +Trigger.dev orchestrates the repository analysis through a single long-running task: + +1. **`analyzeRepo`** – Main task that: + - Clones the repository to a temp directory (shallow clone for speed) + - Spawns a Claude agent with file system tools + - Streams Claude's response in real-time via Trigger.dev Realtime Streams + - Cleans up temp directory on completion or error + +**Process flow:** + +``` +User enters GitHub URL + question + ↓ +API triggers analyzeRepo task + ↓ +Clone repo to temp directory + ↓ +Claude Agent SDK explores codebase with tools + ↓ +Response streams via Trigger.dev Realtime → Frontend + ↓ +Cleanup temp directory +``` + +**Claude's available tools:** + +- **Bash** – Run shell commands to explore the repo +- **Glob** – Find files by pattern (e.g., `**/*.ts`) +- **Grep** – Search file contents with regex +- **Read** – Read file contents + +## Relevant Code + +- **Main analysis task** – Clones repo, runs Claude agent, streams response ([`trigger/analyze-repo.ts`](trigger/analyze-repo.ts)) +- **Stream definition** – Typed stream for real-time text responses ([`trigger/agent-stream.ts`](trigger/agent-stream.ts)) +- **API endpoint** – Triggers the task and returns access token ([`app/api/analyze-repo/route.ts`](app/api/analyze-repo/route.ts)) +- **Response page** – Real-time streaming display with progress ([`app/response/[runId]/page.tsx`](app/response/[runId]/page.tsx)) +- **Landing page** – Repository URL input with example repos ([`app/page.tsx`](app/page.tsx)) +- **Trigger.dev config** – Project settings with external SDK bundle ([`trigger.config.ts`](trigger.config.ts)) + +## Learn More + +- [**Trigger.dev Realtime Streams**](https://trigger.dev/docs/realtime/streams) – Stream data from tasks to your frontend +- [**Trigger.dev React Hooks**](https://trigger.dev/docs/realtime/react-hooks/overview) – `useRealtimeStream` for consuming streams +- [**Claude Agent SDK**](https://platform.claude.com/docs/en/agent-sdk/overview) – Run Claude with agentic tool usage +- [**Trigger.dev schemaTask**](https://trigger.dev/docs/tasks/schemaTask) – Type-safe task payloads with Zod diff --git a/claude-agent-github-wiki/app/api/abort/route.ts b/claude-agent-github-wiki/app/api/abort/route.ts new file mode 100644 index 0000000..267085c --- /dev/null +++ b/claude-agent-github-wiki/app/api/abort/route.ts @@ -0,0 +1,29 @@ +import { runs } from "@trigger.dev/sdk"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + try { + const { runId } = await request.json(); + + // Validate input + if (!runId || typeof runId !== "string") { + return NextResponse.json( + { error: "Run ID is required" }, + { status: 400 } + ); + } + + // Cancel the running task + // This will trigger the AbortController in the task, which propagates to the Claude agent + await runs.cancel(runId); + + return NextResponse.json({ success: true }); + + } catch (error: any) { + console.error("Failed to abort task:", error); + return NextResponse.json( + { error: error.message || "Failed to abort task" }, + { status: 500 } + ); + } +} diff --git a/claude-agent-github-wiki/app/api/analyze-repo/route.ts b/claude-agent-github-wiki/app/api/analyze-repo/route.ts new file mode 100644 index 0000000..2a4abe5 --- /dev/null +++ b/claude-agent-github-wiki/app/api/analyze-repo/route.ts @@ -0,0 +1,54 @@ +import { tasks } from "@trigger.dev/sdk"; +import type { analyzeRepo } from "@/trigger/analyze-repo"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + try { + const { repoUrl, question } = await request.json(); + + // Validate inputs + if (!repoUrl || typeof repoUrl !== "string") { + return NextResponse.json( + { error: "Repository URL is required" }, + { status: 400 }, + ); + } + + if (!question || typeof question !== "string") { + return NextResponse.json( + { error: "Question is required" }, + { status: 400 }, + ); + } + + // Basic GitHub URL validation + const githubUrlPattern = /^https?:\/\/(www\.)?github\.com\/[\w-]+\/[\w.-]+/; + if (!githubUrlPattern.test(repoUrl)) { + return NextResponse.json( + { error: "Invalid GitHub URL format" }, + { status: 400 }, + ); + } + + // Trigger the analyze task + const handle = await tasks.trigger( + "analyze-repo", + { repoUrl, question }, + ); + + // Get public access token from handle (auto-generated, expires in 15 min) + const accessToken = handle.publicAccessToken; + + // Return run details + return NextResponse.json({ + runId: handle.id, + accessToken, + }); + } catch (error: any) { + console.error("Failed to trigger analyze-repo task:", error); + return NextResponse.json( + { error: error.message || "Failed to start analysis" }, + { status: 500 }, + ); + } +} diff --git a/claude-agent-github-wiki/app/globals.css b/claude-agent-github-wiki/app/globals.css new file mode 100644 index 0000000..6cffd7c --- /dev/null +++ b/claude-agent-github-wiki/app/globals.css @@ -0,0 +1,102 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-0.5rem); + } +} + +.animate-bounce { + animation: bounce 0.6s ease-in-out infinite; +} + +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/claude-agent-github-wiki/app/layout.tsx b/claude-agent-github-wiki/app/layout.tsx new file mode 100644 index 0000000..6c8d304 --- /dev/null +++ b/claude-agent-github-wiki/app/layout.tsx @@ -0,0 +1,22 @@ +import './globals.css'; +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'Repo Wiki Chat - Chat with any GitHub repository', + description: 'Ask questions about any GitHub repository and watch AI analyze the code in real-time with live reasoning and tool usage.', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/claude-agent-github-wiki/app/page.tsx b/claude-agent-github-wiki/app/page.tsx new file mode 100644 index 0000000..cab521f --- /dev/null +++ b/claude-agent-github-wiki/app/page.tsx @@ -0,0 +1,301 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Github, + Sparkles, + ChevronDown, + ChevronUp, + AlertCircle, +} from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; + +const exampleQuestions = [ + "What is the architecture of this codebase?", + "How does the authentication system work?", + "What are the main API endpoints?", + "Identify potential security vulnerabilities", + "Explain the data flow in this application", + "What testing strategies are used?", +]; + +const exampleRepos = [ + { + name: "Next.js", + url: "https://github.com/vercel/next.js", + description: "The React Framework for Production", + stars: "120k", + }, + { + name: "React", + url: "https://github.com/facebook/react", + description: "A JavaScript library for building UIs", + stars: "220k", + }, + { + name: "VS Code", + url: "https://github.com/microsoft/vscode", + description: "GitHub repository for VS Code", + stars: "158k", + }, +]; + +export default function Home() { + const [repoUrl, setRepoUrl] = useState(""); + const [question, setQuestion] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [showExamples, setShowExamples] = useState(false); + const [error, setError] = useState(""); + const router = useRouter(); + + const validateGitHubUrl = (url: string): boolean => { + try { + const urlObj = new URL(url); + return ( + urlObj.hostname === "github.com" && + urlObj.pathname.split("/").length >= 3 + ); + } catch { + return false; + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + + if (!repoUrl || !question) { + setError("Please provide both a repository URL and a question."); + return; + } + + if (!validateGitHubUrl(repoUrl)) { + setError("Please provide a valid GitHub repository URL."); + return; + } + + setIsLoading(true); + + try { + const response = await fetch("/api/analyze-repo", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ repoUrl, question }), + }); + + if (!response.ok) { + const errorData = await response.json(); + setError(errorData.error || "Failed to start analysis"); + setIsLoading(false); + return; + } + + const { runId, accessToken } = await response.json(); + + // Navigate to response page with run ID and access token + const params = new URLSearchParams({ + accessToken, + question, + }); + router.push(`/response/${runId}?${params.toString()}`); + } catch (error) { + console.error("Failed to analyze repo:", error); + setError("Failed to start analysis. Please try again."); + setIsLoading(false); + } + }; + + const handleExampleRepo = (url: string) => { + setRepoUrl(url); + // Focus on question field + document.getElementById("question")?.focus(); + }; + + const handleExampleQuestion = (q: string) => { + setQuestion(q); + }; + + return ( +
+
+
+
+ + +
+ +

+ Analyze any GitHub Repository +

+ +

+ Ask questions about any public GitHub repository and get detailed + AI-powered analysis, powered by Trigger.dev and Claude. +

+
+ + + + Repository Analysis + + Enter a GitHub repository URL and ask a question about the + codebase + + + +
+
+ + setRepoUrl(e.target.value)} + className="w-full" + disabled={isLoading} + /> +
+ +
+ +