Adds an "Open in DeepWiki" button on GitHub repo pages, search results, topics, and explore pages
As of
// ==UserScript==
// @name GitHub → DeepWiki Button
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Adds an "Open in DeepWiki" button on GitHub repo pages, search results, topics, and explore pages
// @author Michael Farah
// @license MIT
// @match https://github.com/*
// @grant none
// ==/UserScript==
(function () {
"use strict";
const BUTTON_STYLE = `
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
font-size: 12px;
font-weight: 500;
color: #fff;
background-color: #1a6ae4;
border: none;
border-radius: 6px;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
vertical-align: middle;
margin-left: 6px;
line-height: 20px;
`;
const RESERVED_OWNERS = new Set([
"topics",
"search",
"settings",
"orgs",
"users",
"explore",
"trending",
"marketplace",
"sponsors",
"notifications",
"about",
"pricing",
"enterprise",
"blog",
"readme",
"security",
"contact",
"features",
"join",
"login",
"pulls",
"issues",
"stars",
]);
function getRepoPath(urlPath) {
const match = urlPath.match(/^\/([^/]+)\/([^/]+)\/?$/);
if (!match) return null;
const owner = match[1];
const repo = match[2];
if (RESERVED_OWNERS.has(owner.toLowerCase())) return null;
return `${owner}/${repo}`;
}
function makeButton(repoPath) {
const url = `https://deepwiki.com/${repoPath}`;
const btn = document.createElement("a");
btn.href = url;
btn.target = "_blank";
btn.rel = "noopener noreferrer";
btn.setAttribute("style", BUTTON_STYLE);
btn.className = "deepwiki-btn-injected";
btn.innerHTML = `
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13
-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66
.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15
-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.65 7.65 0 0 1 2-.27
c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12
.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48
0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>
DeepWiki
`;
btn.title = `Open on DeepWiki: ${url}`;
return btn;
}
// ── Repo page ──────────────────────────────────────────────────────────────
function addButtonToRepoPage() {
const repoPath = getRepoPath(location.pathname);
if (!repoPath) return;
if (document.getElementById("deepwiki-repo-btn")) return;
const heading = document.querySelector('[itemprop="name"] a, h1.d-flex a');
if (heading) {
const btn = makeButton(repoPath);
btn.id = "deepwiki-repo-btn";
heading.parentElement.appendChild(btn);
}
}
// ── Shared helper: inject buttons next to a set of anchor elements ─────────
function injectButtonsForLinks(links) {
links.forEach((link) => {
const href = link.getAttribute("href") || "";
const repoPath = getRepoPath(href);
if (!repoPath) return;
if (link.parentElement.querySelector(".deepwiki-btn-injected")) return;
link.parentElement.appendChild(makeButton(repoPath));
});
}
// ── Search results page ────────────────────────────────────────────────────
function addButtonsToSearchResults() {
const links = document.querySelectorAll(
'div[data-testid="results-list"] a[href^="/"], ' +
"li.repo-list-item a.v-align-middle, " +
"div.search-title a"
);
injectButtonsForLinks(links);
}
// ── Topics page (github.com/topics/<name>) ────────────────────────────────
function addButtonsToTopicsPage() {
// Each repo card is an <article>; find all of them
document.querySelectorAll("article.border").forEach((card) => {
// Already injected
if (card.querySelector(".deepwiki-btn-injected")) return;
const links = card.querySelectorAll('h3 a[href^="/"]');
if (links.length < 2) return;
// The second link contains the full repository path (e.g., "/rust-lang/rust")
const repoPath = getRepoPath(links[1].getAttribute("href"));
// If the path is invalid or belongs to a reserved owner, skip it
if (!repoPath) return;
const btn = makeButton(repoPath);
// Append button after the h3 heading, inside the card
const heading = card.querySelector("h3");
if (heading) heading.appendChild(btn);
});
}
// ── Explore page (github.com/explore) ────────────────────────────────────
function addButtonsToExplorePage() {
const links = document.querySelectorAll(
'article a[href^="/"][data-ga-click], ' +
'div.explore-content a.text-bold[href^="/"]'
);
injectButtonsForLinks(links);
}
// ── Router ─────────────────────────────────────────────────────────────────
function run() {
const path = location.pathname;
const search = location.search;
if (path.startsWith("/search") || search.includes("type=repositories")) {
addButtonsToSearchResults();
} else if (path.startsWith("/topics/") || path === "/topics") {
// /topics/<name> — list of repos tagged with that topic
// Note: /topics itself is just a browse page with no repo cards, but
// running the injector is harmless; it simply won't find matching links.
addButtonsToTopicsPage();
} else if (path.startsWith("/explore")) {
addButtonsToExplorePage();
} else {
addButtonToRepoPage();
}
}
run();
// Debounced observer — avoids hammering run() on every tiny DOM mutation
let debounceTimer;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(run, 300);
});
observer.observe(document.body, { childList: true, subtree: true });
})();