GitHub → DeepWiki Button

Adds an "Open in DeepWiki" button on GitHub repo pages, search results, topics, and explore pages

2026-04-03 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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 });
})();