Codebase intelligence in one lens.
RepoLens is a full-stack platform that connects to any GitHub repository, builds an interactive dependency graph from the real file tree, explains every file and folder with AI, and answers questions about the codebase — without leaving your tab.
RepoLens is for developers who spend their first week on any new codebase just figuring out where things live.
It connects to your GitHub account, fetches the entire file tree in a single API call, renders it as a live force-directed graph on a <canvas>, and surfaces AI-powered answers the moment you click anything — before you've opened a single file.
What it does automatically:
- Sign in with GitHub → dashboard shows your repos, collaborations, and org repos in one view
- Click any repository → full dependency graph rendered from the actual file tree
- Click any node → AI explains what it does, what depends on it, and how it fits the architecture
- Ask anything in the chat → full repo context, specific file path references, no generic answers
| Feature | Description |
|---|---|
| ◉ Dependency Graph | Force-directed canvas graph of every folder and file — color-coded by language |
| ✦ AI File Explain | Click any node — Gemini explains what it does, what it imports, and what breaks if you touch it |
| ◫ Repo Chat | Ask anything about the codebase with full file tree context — answers reference real paths |
| ⇄ GitHub OAuth | Sign in once, access every repo you own, collaborate on, or belong to via org membership |
| ↗ Collaboration View | Dashboard splits your repos from collaborations — owner avatar shown on every shared card |
| ⌗ Language Colors | TypeScript blue, Python green, Go cyan — matches GitHub's own dot colors |
| ƒ Symbol Search | Function extraction across the repo — search any symbol and watch call sites light up |
| ◈ Single-Request Tree | Entire repo structure fetched in one API call regardless of depth via GitHub's recursive tree endpoint |
| ⚡ Canvas Renderer | Thousands of nodes at 60fps — no SVG, no D3, no DOM nodes per file |
| ◫ View Modes | Switch between Graph, Tree, Layers, and Timeline layouts on the fly |
Framework → Next.js 14 / React.js 18 (App Router, Server + Client Components)
Language → TypeScript / JavaScript (strict mode)
Runtime → Node.js
Styling → Tailwind CSS (light + dark themes)
Auth → NextAuth.js (GitHub OAuth, accessToken on session)
GitHub API → Octokit REST (repos, git trees, blobs, content)
AI Provider → Google Gemini API (structured JSON analysis + chat)
Prompting → Prompt Engineering (system prompts + JSON schemas)
Graph Renderer → Canvas API (custom force-directed simulation, no D3)
Analysis → Repository Analysis · Code Intelligence · Symbol Extraction
Architecture → Data Structures · Software Architecture
Quality → ESLint (eslint-config-next) + Vitest
Tooling → Git · GitHub · Vercel-ready
┌──────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ │
│ app/page.tsx → Landing + auth redirect │
│ app/signin/page.tsx → GitHub OAuth trigger │
│ app/dashboard/page.tsx → Repo picker (3 affiliation groups)│
│ app/explore/[owner]/ → Graph + AI panel + chat │
│ [repo]/page.tsx │
│ │
│ CanvasGraph.tsx → force simulation + hit testing │
│ FileTree.tsx → folder/file sidebar │
│ AnalysisPanel.tsx → AI explanation + streaming chat │
└──────────────────┬───────────────────────────────────────────┘
│ fetch (REST)
▼
┌──────────────────────────────────────────────────────────────┐
│ Next.js API Routes │
│ │
│ /api/repos → listForAuthenticatedUser │
│ /api/repos/[o]/[r]/tree → getTree (recursive: '1') │
│ /api/repos/[o]/[r]/symbols → symbol extraction + call map │
│ /api/analyze → Gemini node explanation │
│ /api/analyze/overview → repo health summary │
│ /api/chat → chat with full tree context │
│ │
│ All routes read session.accessToken for GitHub access │
└──────────────────┬───────────────────────────────────────────┘
│ accessToken
▼
┌──────────────────────────────────────────────────────────────┐
│ NextAuth.js + GitHub API + Gemini │
│ │
│ signIn("github", { scope: 'read:user repo' }) │
│ session.accessToken → passed to Octokit │
│ octokit.repos.listForAuthenticatedUser(affiliation: all) │
│ octokit.git.getTree({ recursive: '1' }) │
│ geminiGenerate({ system, messages, json }) │
└──────────────────────────────────────────────────────────────┘
- Node.js 18+
- A GitHub OAuth App (
github.com/settings/developers) - A Google Gemini API key
git clone https://github.com/<your-username>/RepoLens
cd RepoLens
npm install
cp .env.example .env.local# .env.local
GITHUB_ID=<your-github-oauth-client-id>
GITHUB_SECRET=<your-github-oauth-client-secret>
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>
GEMINI_API_KEY=<your-gemini-api-key>- Go to
github.com/settings/developers→ New OAuth App - Set Authorization callback URL to
http://localhost:3000/api/auth/callback/github - Copy the Client ID and Client Secret into
.env.local - Scopes requested:
read:user repo(profile + private/public repos)
npm run devOpen http://localhost:3000, sign in with GitHub, and pick a repo.
| Action | What happens |
|---|---|
| Click Sign in on the landing page | GitHub OAuth flow via NextAuth.js |
| Dashboard loads | All your repos, collaborations, and org repos fetched and grouped |
| Click any repo card | Full recursive file tree fetched — graph renders |
| Click a node on the graph | AI panel explains the file or folder |
| Search a function (⌘ K) | Symbol index lights up definitions and call sites in the graph |
| Type in the chat input | Ask anything — Gemini has the full file tree as context |
| Click Sign out | Session cleared, back to landing page |
RepoLens/
├── app/
│ ├── page.tsx # Landing page (scroll sections)
│ ├── signin/page.tsx # GitHub OAuth sign-in
│ ├── dashboard/page.tsx # Repository picker
│ ├── explore/[owner]/[repo]/
│ │ └── page.tsx # Graph explorer
│ ├── api/
│ │ ├── repos/route.ts # List repos (all affiliations)
│ │ ├── repos/[owner]/[repo]/
│ │ │ ├── tree/route.ts # Recursive file tree → graph
│ │ │ └── symbols/route.ts # Symbol extraction + call sites
│ │ ├── analyze/route.ts # Gemini node analysis
│ │ ├── analyze/overview/ # Repo health overview
│ │ └── chat/route.ts # Chat with repo context
│ └── auth/[...nextauth]/route.ts # NextAuth handler
├── components/
│ ├── graph/
│ │ └── CanvasGraph.tsx # Force-directed canvas renderer
│ └── ui/
│ ├── FileTree.tsx # Folder/file sidebar
│ ├── AnalysisPanel.tsx # AI explain + chat panel
│ └── ... # shadcn-style primitives
├── lib/
│ ├── ai/
│ │ ├── gemini.ts # Gemini client + rate limiting
│ │ └── analyzer.ts # Prompt builders + result cache
│ ├── github/client.ts # Octokit wrapper + repo helpers
│ ├── parser/
│ │ ├── tree-builder.ts # Tree → nodes/edges
│ │ └── dependency-parser.ts # import/require edge extraction
│ └── utils/
│ └── colors.ts # Language + folder color palette
├── types/
│ └── index.ts # RepoGraph, GraphNode, SymbolInfo, etc.
└── blog.md # Build write-up
No SVG. No D3. Raw <canvas> with a custom force simulation:
// Every frame: clear → run physics → paint
function tick() {
applyForces(nodes, edges) // spring + repulsion
nodes.forEach(n => {
n.vx *= 0.85 // damping — without this nodes oscillate forever
n.vy *= 0.85
n.x += n.vx
n.y += n.vy
})
drawFrame(ctx, nodes, edges)
requestAnimationFrame(tick)
}Hit testing on click (canvas has no event system):
function getNodeAtPoint(x: number, y: number): Node | null {
for (let i = nodes.length - 1; i >= 0; i--) {
const n = nodes[i]
const dx = x - n.x, dy = y - n.y
if (dx * dx + dy * dy <= n.r * n.r) return n
}
return null
}Handles 1,000-node repos without a frame drop.
Unit tests are written with Vitest and cover the pure logic of the repo:
npm run test # run once
npm run test:watch # watch modeCovered modules:
lib/parser/tree-builder— ignore rules, node/edge construction, language detectionlib/parser/dependency-parser— import resolution, extension/index fallback, dedupelib/ai/gemini— JSON parsing, model fallback chain, retry on 429/5xxlib/ai/analyzer— prompt building, cache hits, graceful error fallbackslib/github/client— repo mapping, affiliation classification, tree/blob fetchinglib/utils—cn()class merging and color helpers
PRs are welcome. For major changes, open an issue first.
npm run dev # development server with HMR
npm run build # production build
npm run lint # ESLint
npm run test # Vitest© 2026 RepoLens · Built with care