Opening a new GitHub repository is like walking into someone else's kitchen. You know there's food somewhere. You don't know which drawer has the knives.
The README tells you how to install it. It rarely tells you how it works: which folder holds the real logic, where the request comes in, why there are three utils folders. So you click around. You open twenty files. Thirty minutes later you have a rough picture and a headache.
DeepWiki was built for that thirty minutes. This post covers what it is, who made it and why, how to use it on any public repository, how to tune your own repo's wiki with Devin, and then the fun part: building a small DeepWiki of your own on your laptop. It's a Hono API that takes a public GitHub URL and returns a summary, a folder index and a chatbot that answers questions with file citations. The models run through OpenRouter.
All the code is at the bottom. It's about 250 lines of TypeScript.
What is DeepWiki?
DeepWiki is an AI-generated wiki for GitHub repositories. You give it a repo, and it reads the code and writes documentation pages: an overview, the architecture, how the main modules connect, diagrams, and links back to the exact source files. It also has a chatbot that answers questions about that repo.
The trick for using it is a single URL swap. Take any public repository URL:
https://github.com/honojs/honoand replace github.com with deepwiki.com:
https://deepwiki.com/honojs/honoThat's it. If the repo has been indexed, you get the wiki. If not, you can submit it for indexing.
Who made DeepWiki, and why
DeepWiki comes from Cognition, the company behind Devin, the AI software engineer. It was announced in April 2025 by Silas Alberti of Cognition. The AINews write-up of the launch describes it as a free encyclopedia of all GitHub repos: a Wikipedia-style page for every public repository, plus a Devin-backed chatbot for asking how to use it.
The why makes sense once you think about what Devin does. An AI engineer can't fix a bug in a codebase it doesn't understand. Before Devin writes a line, it has to build a map of the repository: what the modules are, how they depend on each other, where the entry points are. That map is expensive to build and valuable to keep.
DeepWiki is that map, made public. For Cognition, it shows off how well Devin understands code, and it puts Devin in front of every developer who has ever been lost in a new repo. For the rest of us, it's a free reading assistant for open source.
The deeper reason is that reading code is the bottleneck now, not writing it. AI tools write code fast. Understanding a 400-file repository well enough to change it safely is still slow. Anything that shortens that step, for humans or for agents, is worth a lot. That's the same theme as the agent harness post: agents are only as good as the context you give them.
How to use DeepWiki on a repository
There are three ways to use DeepWiki, from fastest to most powerful.
1. The URL swap (zero setup)
Replace github.com with deepwiki.com in any public repo URL. You get:
- An overview page: what the project is and how it's organised.
- Architecture pages: the main subsystems, each with an explanation and source links.
- Diagrams: module relationships and data flow.
- A chat box: ask "where are routes registered?" or "how does the cache invalidate?" and get an answer grounded in the code.
It's useful before you git clone anything. Use it to decide whether a library is worth adopting, or to find the file you need to read first.
2. The DeepWiki MCP server (for Claude Code, Cursor and other agents)
This is the part most people miss. DeepWiki runs a free MCP server that needs no authentication for public repositories. According to the DeepWiki MCP docs, it exposes three tools:
read_wiki_structurelists the documentation topics for a repo.read_wiki_contentsreturns the documentation itself.ask_questionanswers a question about a repo, grounded in its code.
Add it to Claude Code with one command:
claude mcp add -s user -t http deepwiki https://mcp.deepwiki.com/mcpFor Cursor and most other clients, the config uses a url field:
{
"mcpServers": {
"deepwiki": { "url": "https://mcp.deepwiki.com/mcp" }
}
}The docs point out that Devin's own desktop client uses serverUrl instead, and that using the wrong field name means the server is silently ignored. The old SSE endpoint (/sse) is being deprecated, so use /mcp.
Why this matters: your coding agent can now ask "how does Hono's router pick a match?" about a library you depend on, without you pasting files into the chat. It's the cheapest way to give an agent knowledge of third-party code.
3. Inside Devin, for private repositories
The public site and MCP only cover public repos. For private code you need a Devin account. Devin indexes the repositories you connect and builds wikis for them in its app. For private repos through MCP, the docs say to use the Devin MCP server with a Devin API key instead of the public one.
How to create and steer your own DeepWiki with Devin
If your repository is public, it may already have a DeepWiki page. But the auto-generated structure isn't always how you would explain your project. Devin lets you steer it with a config file.
According to Devin's DeepWiki docs, you add a .devin/wiki.json file to the repository. It has two required fields:
repo_notes: context for the generator, such as "the real logic lives inpackages/core;apps/are demos". Each note can be up to 10,000 characters. Use[]if you have none.pages: the pages you want. Each has a uniquetitle, apurpose, an optionalparentfor nesting, and optionalpage_notes.
Here's what it could look like for a repo like StackBudget:
{
"repo_notes": [
{
"content": "StackBudget is a Claude Code skill. The scanner is in skills/stack-budget/scripts, pricing data lives in assets/engine.js and references/pricing.md. examples/ holds generated reports, not source."
}
],
"pages": [
{ "title": "Overview", "purpose": "What StackBudget does and who it is for" },
{ "title": "How the scanner works", "purpose": "How manifests, env keys and configs become stack.json", "parent": "Overview" },
{ "title": "Pricing engine", "purpose": "How rates are stored, dated and applied to presets" },
{ "title": "Adding a new provider", "purpose": "Step-by-step guide for contributors" }
]
}Things to know before you rely on it:
- When the file exists, Devin skips its own planning and generates only the pages you list. Any folder you don't cover won't be documented.
- Limits: 30 pages (80 on enterprise), 100 notes in total, 10,000 characters per note.
- Effort levels: Low is free. Medium costs about 5–10 ACUs per wiki and High about 20–40 ACUs, and both need a subscription or credits.
- Regenerate after you commit the file to pick up the changes.
If you maintain open-source projects, this is worth ten minutes. People will find your repo's DeepWiki page whether you set it up or not, so it might as well say what you want it to say.
Why build your own DeepWiki?
DeepWiki is free and good, so why build one?
- Private code without sending it to another vendor. Your own version can run against any model you choose through OpenRouter, with your own key and your own logging.
- It's yours to embed. Put a "chat with this repo" box on your open-source project's docs site, or inside an internal tool.
- You control the output. You pick the summary format, the index shape and the system prompt. For example: "always say how to run it locally first".
- You learn retrieval properly. Building it teaches you the whole retrieval-augmented generation (RAG) loop: fetch, chunk, embed, retrieve, answer with citations. That loop shows up everywhere. It's the same idea I used when generating summaries and embeddings for 300 articles here on ihatereading.
There's also a well-known open-source attempt worth knowing: DeepWiki-Open (MIT, last commit September 2026). It's a full app with a UI, diagrams and support for GitHub, GitLab and Bitbucket. Ours is smaller on purpose: an API you can read in one sitting and plug into anything.
Build a local DeepWiki with Hono and OpenRouter
Here's the plan. One Hono server, four endpoints:
| Endpoint | What it does |
|---|---|
POST /wiki with { "url": "https://github.com/owner/repo" } | Fetches the repo, writes a summary, builds a folder index, embeds the code and caches it all to disk |
GET /wiki/:owner/:repo/summary | Returns the overview: what it is, stack, how to run it, key folders |
GET /wiki/:owner/:repo/index | Returns one line of explanation per top-level folder |
POST /wiki/:owner/:repo/chat with { "question": "..." } | Streams an answer over Server-Sent Events, with the source files it used |
Why these tools:
- Hono is a tiny, fast web framework that runs on Node, Bun, Deno and Cloudflare Workers with the same code. It has streaming helpers built in, which we need for the chatbot.
- OpenRouter gives one API key for hundreds of models. It speaks the OpenAI API format, so the official
openaiSDK works if you change thebaseURL. It also has an embeddings endpoint, so chat and embeddings go through one key. I've used OpenRouter before for the Instagram carousel pipeline. It's the easiest way to swap models without rewriting code. - No vector database. Embeddings go into a JSON file per repo, and search is a plain cosine-similarity loop. For a few thousand chunks, that's milliseconds. Add pgvector or LanceDB later if you outgrow it.
Step 0: set up the project
mkdir localwiki && cd localwiki
npm init -y
npm i hono @hono/node-server openai
npm i -D typescript tsx @types/nodeSet "type": "module" in package.json, then add your keys:
export OPENROUTER_API_KEY=sk-or-...
export GITHUB_TOKEN=ghp_... # optional, but strongly recommendedThe GitHub token matters. Without one, the GitHub REST API allows only 60 requests an hour. With a personal access token you get 5,000. We only make two API calls per repo, the repo info and the file tree, and download the actual files from raw.githubusercontent.com. Raw downloads don't count against the REST rate limit, but a token still saves you from surprises.
The project layout:
localwiki/
├── src/
│ ├── github.ts # fetch tree + files from a public repo
│ ├── llm.ts # OpenRouter client: chat + embeddings
│ ├── store.ts # JSON file store, chunking, cosine search
│ ├── wiki.ts # build step: summary, index, embeddings
│ └── index.ts # Hono routes
└── data/ # cached wikis (created on first run)Step 1: fetch the repository (src/github.ts)
We take a URL or an owner/repo string, ask GitHub for the default branch and the full file tree, then filter the tree down to text files worth reading. Lock files, node_modules, build output and anything over 60 KB are skipped, because they burn tokens and tell the model nothing.
Files are sorted so the README and manifest (package.json, pyproject.toml, go.mod) always come first. The summary depends on them.
// Pull a public repo's file tree and the text files worth reading.
const GH = "https://api.github.com";
const headers: Record<string, string> = {
Accept: "application/vnd.github+json",
"User-Agent": "localwiki",
...(process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}),
};
const SKIP_DIRS = /(^|\/)(node_modules|dist|build|\.next|vendor|coverage|\.git)\//;
const SKIP_FILES = /(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|\.min\.js|\.map)$/;
const TEXT_EXT = /\.(md|mdx|ts|tsx|js|jsx|mjs|py|go|rs|rb|java|kt|swift|php|vue|svelte|astro|json|toml|yaml|yml|sql|sh|css)$/i;
const MAX_BYTES = 60_000; // skip giant generated files
const MAX_FILES = 300; // keep the first version cheap
export type RepoFile = { path: string; content: string };
export type RepoMeta = { owner: string; repo: string; branch: string; description: string | null; stars: number };
export function parseRepoUrl(url: string) {
const m = url.match(/github\.com\/([^/]+)\/([^/#?]+)/) ?? url.match(/^([^/]+)\/([^/]+)$/);
if (!m) throw new Error("Pass a GitHub URL or owner/repo");
return { owner: m[1], repo: m[2].replace(/\.git$/, "") };
}
async function gh<T>(path: string): Promise<T> {
const res = await fetch(`${GH}${path}`, { headers });
if (!res.ok) throw new Error(`GitHub ${res.status} on ${path}`);
return res.json() as Promise<T>;
}
export async function fetchRepo(owner: string, repo: string) {
const info = await gh<{ default_branch: string; description: string | null; stargazers_count: number }>(
`/repos/${owner}/${repo}`,
);
const branch = info.default_branch;
const tree = await gh<{ tree: { path: string; type: string; size?: number }[]; truncated: boolean }>(
`/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`,
);
const allPaths = tree.tree.filter((t) => t.type === "blob").map((t) => t.path);
const wanted = tree.tree
.filter((t) => t.type === "blob" && (t.size ?? 0) < MAX_BYTES)
.filter((t) => TEXT_EXT.test(t.path) && !SKIP_DIRS.test(t.path) && !SKIP_FILES.test(t.path))
// README and manifests first, so the summary always has them
.sort((a, b) => score(b.path) - score(a.path))
.slice(0, MAX_FILES);
// raw.githubusercontent.com doesn't count against the REST API rate limit
const files: RepoFile[] = [];
for (const batch of chunk(wanted, 10)) {
const got = await Promise.all(
batch.map(async (t) => {
const r = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${t.path}`);
return r.ok ? { path: t.path, content: await r.text() } : null;
}),
);
files.push(...(got.filter(Boolean) as RepoFile[]));
}
const meta: RepoMeta = { owner, repo, branch, description: info.description, stars: info.stargazers_count };
return { meta, allPaths, files, truncated: tree.truncated };
}
function score(p: string) {
if (/^readme/i.test(p)) return 100;
if (/^(package\.json|pyproject\.toml|go\.mod|cargo\.toml)$/i.test(p)) return 90;
if (/(^|\/)(index|main|app|server)\.(ts|js|py|go)$/.test(p)) return 50;
return 10 - p.split("/").length; // shallow files before deep ones
}
const chunk = <T,>(a: T[], n: number) =>
Array.from({ length: Math.ceil(a.length / n) }, (_, i) => a.slice(i * n, i * n + n));Step 2: talk to models through OpenRouter (src/llm.ts)
One client covers both jobs. The chat model writes the summary, the index lines and the chatbot answers. The embedding model turns code chunks into vectors. Both are environment variables, so switching models is a config change, not a code change.
// OpenRouter speaks the OpenAI API, so the official SDK works with a different baseURL.
import OpenAI from "openai";
export const ai = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: { "HTTP-Referer": "http://localhost:8787", "X-Title": "localwiki" },
});
export const CHAT_MODEL = process.env.CHAT_MODEL ?? "google/gemini-2.5-flash";
export const EMBED_MODEL = process.env.EMBED_MODEL ?? "openai/text-embedding-3-small";
export async function complete(system: string, user: string) {
const r = await ai.chat.completions.create({
model: CHAT_MODEL,
messages: [{ role: "system", content: system }, { role: "user", content: user }],
});
return r.choices[0]?.message?.content ?? "";
}
export async function embed(texts: string[]) {
const out: number[][] = [];
for (let i = 0; i < texts.length; i += 64) {
const r = await ai.embeddings.create({ model: EMBED_MODEL, input: texts.slice(i, i + 64) });
out.push(...r.data.map((d) => d.embedding));
}
return out;
}A fast, cheap chat model is the right default here. Summaries and one-line folder descriptions don't need a frontier model. Save the expensive model for the chatbot if you want, by giving it its own variable.
Step 3: store, chunk and search (src/store.ts)
Chunking is where most RAG projects go wrong. Ours is deliberately simple: 1,500 characters per chunk with 200 characters of overlap, so a function that crosses a boundary still appears whole in at least one chunk. Every chunk keeps its file path and offset, which is what lets the chatbot cite sources.
// The "vector DB" is a JSON file per repo. Fine for a few thousand chunks.
import { mkdir, readFile, writeFile } from "node:fs/promises";
import type { RepoMeta } from "./github.js";
export type Chunk = { path: string; start: number; text: string; vector: number[] };
export type Wiki = {
meta: RepoMeta;
summary: string;
index: { folder: string; files: string[]; about: string }[];
chunks: Chunk[];
builtAt: string;
};
const dir = "./data";
const file = (o: string, r: string) => `${dir}/${o}__${r}.json`;
export async function save(w: Wiki) {
await mkdir(dir, { recursive: true });
await writeFile(file(w.meta.owner, w.meta.repo), JSON.stringify(w));
}
export async function load(o: string, r: string): Promise<Wiki | null> {
try { return JSON.parse(await readFile(file(o, r), "utf8")); } catch { return null; }
}
export function splitIntoChunks(path: string, content: string, size = 1500, overlap = 200) {
const out: { path: string; start: number; text: string }[] = [];
for (let i = 0; i < content.length; i += size - overlap) {
out.push({ path, start: i, text: content.slice(i, i + size) });
if (i + size >= content.length) break;
}
return out;
}
export function topK(chunks: Chunk[], q: number[], k = 8) {
const cos = (a: number[], b: number[]) => {
let d = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) { d += a[i] * b[i]; na += a[i] ** 2; nb += b[i] ** 2; }
return d / (Math.sqrt(na) * Math.sqrt(nb));
};
return chunks.map((c) => ({ c, s: cos(c.vector, q) })).sort((x, y) => y.s - x.s).slice(0, k).map((x) => x.c);
}Step 4: build the wiki (src/wiki.ts)
This is the DeepWiki part. It runs three passes:
- Summary. The file tree, README and manifest go to the model with a strict prompt: use only what's given, and say "not clear from the code" instead of guessing. The output has four fixed sections, so every repo's summary has the same shape.
- Index. Files are grouped by top-level folder. The model sees each folder's file list plus the first part of up to four files, and writes one sentence on what the folder does. This is the "where do I look first" map.
- Embeddings. Every chunk is embedded with its file path prepended. Including the path helps a lot: a question about "routing" pulls chunks from
src/router/even when the word "routing" isn't in the code.
// Build step: summary + folder index + embedded chunks.
import { fetchRepo } from "./github.js";
import { complete, embed } from "./llm.js";
import { splitIntoChunks, type Wiki } from "./store.js";
export async function buildWiki(owner: string, repo: string): Promise<Wiki> {
const { meta, allPaths, files } = await fetchRepo(owner, repo);
// 1. Summary: README + manifest + the file tree is usually enough
const readme = files.find((f) => /^readme/i.test(f.path))?.content.slice(0, 12_000) ?? "";
const manifest = files.find((f) => /^(package\.json|pyproject\.toml|go\.mod|cargo\.toml)$/i.test(f.path))?.content.slice(0, 4_000) ?? "";
const summary = await complete(
"You write short, accurate repository overviews for developers. Use only the material given. Say 'not clear from the code' instead of guessing.",
`Repo: ${owner}/${repo}\nDescription: ${meta.description ?? "none"}\n\nFILE TREE:\n${allPaths.slice(0, 400).join("\n")}\n\nMANIFEST:\n${manifest}\n\nREADME:\n${readme}\n\n` +
"Write: 1) what it is in two sentences, 2) tech stack, 3) how to run it, 4) the 5 most important folders and why.",
);
// 2. Index: one line per top-level folder
const folders = new Map<string, string[]>();
for (const f of files) {
const top = f.path.includes("/") ? f.path.split("/")[0] : "(root)";
folders.set(top, [...(folders.get(top) ?? []), f.path]);
}
const index = await Promise.all(
[...folders].map(async ([folder, paths]) => {
const sample = files.filter((f) => paths.includes(f.path)).slice(0, 4)
.map((f) => `--- ${f.path}\n${f.content.slice(0, 1_500)}`).join("\n");
const about = await complete(
"Describe what this folder does in one sentence. No fluff.",
`Folder: ${folder}\nFiles: ${paths.join(", ")}\n\n${sample}`,
);
return { folder, files: paths, about };
}),
);
// 3. Chunks + embeddings for the chatbot
const pieces = files.flatMap((f) => splitIntoChunks(f.path, f.content));
const vectors = await embed(pieces.map((p) => `${p.path}\n${p.text}`));
const chunks = pieces.map((p, i) => ({ ...p, vector: vectors[i] }));
return { meta, summary, index, chunks, builtAt: new Date().toISOString() };
}Step 5: the Hono API and the streaming chatbot (src/index.ts)
The chat endpoint does the retrieval loop. It embeds the question, takes the eight closest chunks, and sends them to the model with the summary as extra context. It streams the answer back over Server-Sent Events using Hono's streamSSE helper.
It sends a sources event first, so your UI can show "reading src/router/trie.ts, src/hono-base.ts…" before the first word of the answer arrives. That small detail makes it feel like DeepWiki rather than a black box.
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { streamSSE } from "hono/streaming";
import { parseRepoUrl } from "./github.js";
import { buildWiki } from "./wiki.js";
import { load, save, topK } from "./store.js";
import { ai, CHAT_MODEL, embed } from "./llm.js";
const app = new Hono();
// POST /wiki { "url": "https://github.com/honojs/hono" } -> builds and caches the wiki
app.post("/wiki", async (c) => {
const { url } = await c.req.json<{ url: string }>();
const { owner, repo } = parseRepoUrl(url);
const cached = await load(owner, repo);
if (cached) return c.json({ status: "cached", repo: `${owner}/${repo}`, builtAt: cached.builtAt });
const wiki = await buildWiki(owner, repo);
await save(wiki);
return c.json({ status: "built", repo: `${owner}/${repo}`, chunks: wiki.chunks.length });
});
app.get("/wiki/:owner/:repo/summary", async (c) => {
const w = await load(c.req.param("owner"), c.req.param("repo"));
return w ? c.json({ meta: w.meta, summary: w.summary }) : c.json({ error: "build it first" }, 404);
});
app.get("/wiki/:owner/:repo/index", async (c) => {
const w = await load(c.req.param("owner"), c.req.param("repo"));
return w ? c.json(w.index) : c.json({ error: "build it first" }, 404);
});
// POST /wiki/:owner/:repo/chat { "question": "..." } -> streamed answer with file citations
app.post("/wiki/:owner/:repo/chat", async (c) => {
const w = await load(c.req.param("owner"), c.req.param("repo"));
if (!w) return c.json({ error: "build it first" }, 404);
const { question } = await c.req.json<{ question: string }>();
const [qv] = await embed([question]);
const hits = topK(w.chunks, qv, 8);
const context = hits.map((h) => `### ${h.path} (offset ${h.start})\n${h.text}`).join("\n\n");
return streamSSE(c, async (stream) => {
await stream.writeSSE({ event: "sources", data: JSON.stringify([...new Set(hits.map((h) => h.path))]) });
const res = await ai.chat.completions.create({
model: CHAT_MODEL,
stream: true,
messages: [
{
role: "system",
content: `You answer questions about ${w.meta.owner}/${w.meta.repo}. Use only the code context. Cite file paths in backticks. If the context doesn't contain the answer, say so.\n\nRepo summary:\n${w.summary}`,
},
{ role: "user", content: `${question}\n\nCODE CONTEXT:\n${context}` },
],
});
for await (const part of res) {
const token = part.choices[0]?.delta?.content;
if (token) await stream.writeSSE({ event: "token", data: token });
}
await stream.writeSSE({ event: "done", data: "" });
});
});
serve({ fetch: app.fetch, port: 8787 }, (i) => console.log(`localwiki on http://localhost:${i.port}`));Step 6: run it
npx tsx src/index.ts
# localwiki on http://localhost:8787Build a wiki for a repo:
curl -X POST localhost:8787/wiki \
-H "content-type: application/json" \
-d '{"url":"https://github.com/honojs/hono"}'Read the summary and the index:
curl localhost:8787/wiki/honojs/hono/summary
curl localhost:8787/wiki/honojs/hono/indexAsk the chatbot. -N turns off buffering so you see the stream arrive:
curl -N -X POST localhost:8787/wiki/honojs/hono/chat \
-H "content-type: application/json" \
-d '{"question":"How does Hono pick which router to use?"}'You'll get a sources event with file paths, then token events with the answer, then done. Point a small React page at it and you have your own DeepWiki UI.
Status of this code: it typechecks against hono 4.13, @hono/node-server 2.1 and openai 7.39. I haven't run the live GitHub + OpenRouter round-trip end to end in my sandbox, because it blocks outbound calls to both. Run it with your own keys and open an issue on the repo if something breaks.
How to make it better (the honest list)
The 250-line version works. Here's what separates it from the real DeepWiki, in order of value:
- Chunk by code structure, not characters. Split on functions and classes using tree-sitter, so chunks follow the code's own boundaries. This is the single biggest quality jump.
- Pack the repo smarter for big summaries. Tools like Repomix and gitingest turn a whole repo into one LLM-friendly text file. Use them for the summary pass on repos that fit in a long-context model.
- Write real pages, not one-liners. Loop over the index and ask the model for a full page per folder, with links between pages. That's the actual "wiki".
- Generate diagrams. Ask the model for Mermaid flowcharts of module dependencies and render them in your UI.
- Rebuild on push. Store the commit SHA, and add a GitHub webhook or a daily job that rebuilds only when the SHA changes.
- Cache the index separately from embeddings. Summaries are cheap to regenerate; embeddings for big repos are the expensive part.
- Expose it as an MCP server. Wrap the three routes as MCP tools and your coding agent gets your private DeepWiki, the same way it gets the public one. Hono runs on Cloudflare Workers, so deploying it is cheap. I covered why cheap hosting matters in keeping ihatereading under $100/month.
On cost: the build step is one summary call, one call per top-level folder, and embeddings for every chunk. Small embedding models are cheap per token, so for a typical small-to-mid repo the cost is usually dominated by the chat calls. If you're turning this into a hosted product, run StackBudget on it to see the monthly bill at launch, growth and scale before you open it to the public.
DeepWiki vs. your own: which should you use?
| You want to… | Use |
|---|---|
| Understand a popular open-source library before adopting it | deepwiki.com (URL swap) |
| Give Claude Code or Cursor knowledge of a dependency | DeepWiki MCP (https://mcp.deepwiki.com/mcp) |
| Shape how your public repo is explained | .devin/wiki.json |
| Private repos, inside a team already paying for Devin | Devin |
| Private repos, your own models, your own UI, embedded anywhere | Build it (this post) or DeepWiki-Open |
For most days, the URL swap is enough. Build your own when you need control, privacy, or a "chat with this repo" box inside your own product.
If you're hunting for repos worth running this on, the GitHub trending page on ihatereading is a good place to start. And if you want an agent to research and summarise sources at scale, not just one repo, the AI agent system that researches 100 blogs a day uses the same fetch, summarise and index loop on a different input.
New posts like this one go out in the monthly ihatereading magazine.