@weavetab/plugin-github 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 fy2ne
3
+ Copyright (c) 2026 fy2ne <hello@fy2ne.me>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,24 +1,20 @@
1
1
  # `@weavetab/plugin-github`
2
2
 
3
- > Official [Weavetab](https://weavetab.dev) plugin that gives the agent first-class GitHub
4
- > access — analyze repositories, read raw source, search and inspect issues, and examine pull
5
- > requests — **without burning browser turns**.
3
+ > Official [Weavetab](https://weavetab.dev) plugin that gives the agent first-class, modular GitHub
4
+ > access — analyze repositories, read raw source, search issues, examine pull requests, fetch raw diffs,
5
+ > and inspect active tabs directly — **without burning browser turns or API quota**.
6
6
 
7
7
  [![Trust: Official](https://img.shields.io/badge/trust-official-1e3a8a.svg?style=flat-square)](https://weavetab.dev)
8
- [![Engine: Weavetab ^2.5.1](https://img.shields.io/badge/engine-weavetab_%5E2.5.1-black.svg?style=flat-square)](https://weavetab.dev)
8
+ [![Engine: Weavetab ^2.5.2](https://img.shields.io/badge/engine-weavetab_%5E2.5.2-black.svg?style=flat-square)](https://weavetab.dev)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE)
10
10
 
11
11
  ---
12
12
 
13
13
  ## Why
14
14
 
15
- GitHub support ships as an official plugin rather than a built-in tool: the core server stays lean
16
- at **41 built-in tools**, and GitHub is delivered as the **independently versioned**
17
- `@weavetab/plugin-github` package that can be installed, updated and removed like any other plugin.
18
- It registers a single `github` tool (actions: `analyze`, `read`, `issues`, `pr`).
15
+ GitHub capabilities ship as an official modular plugin rather than polluting the core server. The tools are cleanly separated into dedicated, descriptive MCP tools prefixed with `github_`.
19
16
 
20
- All GitHub data is fetched directly over HTTP (`api.github.com` / `raw.githubusercontent.com`),
21
- so the active browser tab is **never navigated or mutated** and no browser turns are consumed.
17
+ In addition to standard GitHub REST capabilities, the plugin includes **quota-free tools** (`github_read` direct branch reads, `github_diff`, `github_active_tab`) that operate completely outside GitHub's unauthenticated 60 requests/hour REST API rate limit and support private repositories currently active in the user's browser.
22
18
 
23
19
  ## Install
24
20
 
@@ -31,50 +27,80 @@ weavetab plugin add @weavetab/plugin-github
31
27
  Because the package name starts with `@weavetab/plugin-`, it is automatically treated as
32
28
  **Official** — fully trusted, no verification prompt.
33
29
 
34
- Restart the Weavetab MCP server to activate the tool.
30
+ Restart the Weavetab MCP server to activate the tools.
31
+
32
+ ## The Tools
33
+
34
+ ### 1. `github_analyze`
35
+ High-level repository reconnaissance: metadata, default branch, recursive file tree, and README preview. Results are cached in isolated storage (configurable TTL).
36
+ - **Parameters**: `repo` *(optional, falls back to active tab URL)*.
37
+
38
+ ### 2. `github_read` *(Zero REST API Quota)*
39
+ Fetch raw file contents from `raw.githubusercontent.com`.
40
+ - **Parameters**:
41
+ - `paths` *(required)*: Array of file paths relative to repo root.
42
+ - `repo` *(optional)*: Target repository slug or URL.
43
+ - `branch` *(optional)*: Explicit branch/tag. Avoids API discovery calls.
44
+ - `line_range` *(optional)*: `[start, end]` 1-indexed lines.
45
+
46
+ ### 3. `github_issues`
47
+ Search, list, or inspect issues and timeline comments.
48
+ - **Parameters**:
49
+ - `repo` *(optional)*: Target repository slug or URL.
50
+ - `number` *(optional)*: Specific issue number for full details & comments.
51
+ - `search` *(optional)*: Search query string.
52
+ - `labels` *(optional)*: Filter by labels.
53
+ - `state` *(optional)*: `open` · `closed` · `all`.
54
+ - `limit` *(optional)*: Max issues to return (default `5`).
55
+
56
+ ### 4. `github_pull_request`
57
+ Examine pull request details, changed files, diff stats, and comments, or list recent pull requests.
58
+ - **Parameters**:
59
+ - `repo` *(optional)*: Target repository slug or URL.
60
+ - `number` *(optional)*: PR number for full details & changed files list.
61
+ - `state` *(optional)*: `open` · `closed` · `all`.
62
+ - `limit` *(optional)*: Max PRs when listing (default `10`).
63
+
64
+ ### 5. `github_diff` *(Zero API Quota)*
65
+ Fetch raw git diffs or patches directly from `github.com` without consuming REST API rate limits.
66
+ - **Parameters**:
67
+ - `repo` *(optional)*: Target repository slug or URL.
68
+ - `pull_number` *(optional)*: Pull request number to fetch diff or patch for.
69
+ - `commit_sha` *(optional)*: Commit SHA to fetch diff or patch for.
70
+ - `base` + `head` *(optional)*: Compare range.
71
+ - `format` *(optional)*: `diff` (unified diff) or `patch` (git patch format). Default `diff`.
72
+
73
+ ### 6. `github_active_tab` *(Zero API Quota & Private Repo Support)*
74
+ Inspect the active browser tab on `github.com` via local CDP evaluation. Extracts repository overview, open PR/issue discussion, or file content directly from the browser DOM with ZERO network or API calls.
75
+ - **Parameters**:
76
+ - `scope` *(optional)*: `auto` · `summary` · `files` · `comments` · `raw`.
35
77
 
36
- ## The `github` tool
37
-
38
- A single, well-organized action-dispatch tool.
78
+ ---
39
79
 
40
- | Parameter | Applies to | Description |
41
- |---|---|---|
42
- | `action` *(required)* | all | `analyze` · `read` · `issues` · `pr` |
43
- | `repo` | all | `owner/repo` or a full `github.com` URL. Omit to use the active tab's repo. |
44
- | `paths` | `read` | File paths relative to the repo root. |
45
- | `line_range` | `read` | Optional `[start, end]`, 1-indexed. |
46
- | `number` | `issues`, `pr` | Issue / PR number for full detail + comments. |
47
- | `search` | `issues` | Free-text query (GitHub Search API, scoped to the repo). |
48
- | `labels` | `issues` | Restrict to issues carrying these labels. |
49
- | `state` | `issues` | `open` · `closed` · `all` |
50
- | `limit` | `issues` | Max issues returned (default `5`). |
51
-
52
- ### Actions
53
-
54
- - **`analyze`** — repository metadata, recursive default-branch file tree, and a README snippet.
55
- Results are cached in the plugin's isolated storage (TTL configurable).
56
- - **`read`** — raw contents of the requested `paths`, with optional line ranges and automatic
57
- truncation for large files.
58
- - **`issues`** — inspect one issue with its comments (`number`), or list / search issues.
59
- - **`pr`** — inspect one pull request with changed files, diff stats and comments (`number`),
60
- or list recent pull requests.
61
-
62
- ### Examples
80
+ ## Examples
63
81
 
64
82
  ```jsonc
65
- // Analyze a repo
66
- { "action": "analyze", "repo": "weavetab/mcp" }
83
+ // Analyze repository structure
84
+ github_analyze({ "repo": "weavetab/mcp" })
67
85
 
68
- // Read two files, lines 1-40 of the first
69
- { "action": "read", "repo": "weavetab/mcp", "paths": ["src/index.ts", "README.md"], "line_range": [1, 40] }
86
+ // Read file directly without consuming API rate limit
87
+ github_read({ "repo": "weavetab/mcp", "paths": ["src/index.ts"], "line_range": [1, 50] })
70
88
 
71
89
  // Search issues by label
72
- { "action": "issues", "repo": "weavetab/mcp", "search": "rate limit", "labels": ["bug"], "state": "open" }
90
+ github_issues({ "repo": "weavetab/mcp", "labels": ["bug"], "state": "open" })
73
91
 
74
- // Inspect PR #42
75
- { "action": "pr", "repo": "weavetab/mcp", "number": 42 }
92
+ // Inspect Pull Request #42
93
+ github_pull_request({ "repo": "weavetab/mcp", "number": 42 })
94
+
95
+ // Fetch raw pull request diff (0 API quota used)
96
+ github_diff({ "repo": "weavetab/mcp", "pull_number": 42, "format": "diff" })
97
+
98
+ // Extract active GitHub tab data from browser DOM
99
+ github_active_tab({ "scope": "auto" })
76
100
  ```
77
101
 
102
+ ---
103
+
78
104
  ## Configuration
79
105
 
80
106
  Written to `~/.weavetab/plugins/@weavetab_plugin-github/config.json` on first load:
@@ -83,55 +109,25 @@ Written to `~/.weavetab/plugins/@weavetab_plugin-github/config.json` on first lo
83
109
  {
84
110
  "cacheTtlMs": 1800000,
85
111
  "maxReadLines": 200,
86
- "defaultIssueLimit": 5
112
+ "defaultIssueLimit": 5,
113
+ "token": ""
87
114
  }
88
115
  ```
89
116
 
90
117
  | Key | Default | Meaning |
91
118
  |---|---|---|
92
119
  | `cacheTtlMs` | `1800000` (30 min) | How long an `analyze` result stays cached. |
93
- | `maxReadLines` | `200` | Files longer than this are truncated when no `line_range` is given. |
94
- | `defaultIssueLimit` | `5` | Default `limit` for the `issues` action. |
120
+ | `maxReadLines` | `200` | Max lines returned before truncation. |
121
+ | `defaultIssueLimit` | `5` | Default issue count limit. |
122
+ | `token` | `""` | Optional GitHub Personal Access Token (`GITHUB_TOKEN`), bumps rate limit from 60 to 5,000 req/hr. |
123
+
124
+ ---
95
125
 
96
126
  ## Permissions
97
127
 
98
128
  | Permission | Why |
99
129
  |---|---|
100
130
  | `config.read` / `config.write` | Read and persist isolated plugin settings. |
101
- | `cdp.evaluate` | Read the active tab URL **only**, to infer the target repo when `repo` is omitted. |
102
- | `network: api.github.com`, `raw.githubusercontent.com`, `github.com` | Fetch public repo data over HTTP. |
103
- | `tools.add` | Register the single `github` tool. |
104
-
105
- No filesystem access beyond the plugin's own sandbox, no HUD/overlay injection, no tab mutation.
106
-
107
- ## Development
108
-
109
- ```bash
110
- npm install
111
- npm run build # tsc -> dist/
112
- npm test # node:test manifest + schema + handler smoke tests
113
- npm run dev # tsc --watch
114
- ```
115
-
116
- ### Project layout
117
-
118
- ```text
119
- plugin-github/
120
- ├── src/index.ts # definePlugin + registerTool("github")
121
- ├── src/github.ts # action dispatcher
122
- ├── src/{analyze,read,issues,pr}.ts
123
- ├── src/{repoContext,cache,rateLimiter,types}.ts
124
- ├── weavetab.json # manifest (permissions + tool schema)
125
- ├── test/ # node:test smoke tests
126
- └── dist/ # compiled output (generated)
127
- ```
128
-
129
- > **Local development note.** Inside the Weavetab monorepo this package pins
130
- > `"@weavetab/sdk": "file:../../sdk"` so it builds against the in-repo SDK. In the standalone
131
- > published repository, pin the released SDK instead: `"@weavetab/sdk": "^2.5.1"`.
132
-
133
- ---
134
-
135
- ## License
136
-
137
- MIT © [Weavetab](https://github.com/Weavetab) & [fy2ne](https://github.com/fy2ne)
131
+ | `cdp.evaluate` | Reads URL and DOM on github.com without navigating or mutating. |
132
+ | `network.domains` | `api.github.com`, `raw.githubusercontent.com`, `github.com`. |
133
+ | `tools.add` | Registers the 6 `github_*` MCP tools. |
@@ -0,0 +1,9 @@
1
+ import type { GithubActiveTabArgs, GithubError, GithubRunContext } from "./types.js";
2
+ /**
3
+ * `github_active_tab` — extract structured information from the active browser
4
+ * tab when open on github.com.
5
+ *
6
+ * Runs entirely via local CDP evaluation with ZERO API calls. Works on both public
7
+ * and private repositories that the user is authenticated to view in their browser.
8
+ */
9
+ export declare function githubActiveTab(ctx: GithubRunContext, args?: GithubActiveTabArgs): Promise<any | GithubError>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `github_active_tab` — extract structured information from the active browser
3
+ * tab when open on github.com.
4
+ *
5
+ * Runs entirely via local CDP evaluation with ZERO API calls. Works on both public
6
+ * and private repositories that the user is authenticated to view in their browser.
7
+ */
8
+ export async function githubActiveTab(ctx, args = {}) {
9
+ const session = ctx.session;
10
+ if (!session) {
11
+ return {
12
+ error: "NO_ACTIVE_SESSION",
13
+ message: "No active browser session connected. Open a github.com page in Weavetab.",
14
+ };
15
+ }
16
+ const requestedScope = args.scope || "auto";
17
+ const evalExpression = `(() => {
18
+ try {
19
+ const url = new URL(window.location.href);
20
+ if (!url.hostname.includes("github.com")) {
21
+ return { error: "NOT_ON_GITHUB", message: "Active tab is not on github.com (" + url.hostname + ")" };
22
+ }
23
+
24
+ const parts = url.pathname.split("/").filter(Boolean);
25
+ if (parts.length < 2) {
26
+ return {
27
+ url: url.href,
28
+ page_type: "github_home_or_profile",
29
+ title: document.title,
30
+ };
31
+ }
32
+
33
+ const owner = parts[0];
34
+ const repo = parts[1];
35
+ const section = parts[2] || "root";
36
+ const idOrBranch = parts[3] || "";
37
+ const scope = ${JSON.stringify(requestedScope)};
38
+
39
+ const res = {
40
+ url: url.href,
41
+ owner,
42
+ repo,
43
+ page_type: section === "root" ? "repo_overview" : section,
44
+ title: document.title,
45
+ };
46
+
47
+ // Pull Request or Issue views
48
+ if (section === "pull" || section === "issues") {
49
+ const titleEl = document.querySelector("bdi.markdown-title, .gh-header-title, [data-testid='issue-title'], h1");
50
+ const stateBadge = document.querySelector("[data-testid='state-label'], [data-testid='issue-state-badge'], .State, [data-state]");
51
+ const bodyEl = document.querySelector("[data-testid='comment-body'], .comment-body");
52
+
53
+ // Files scope on PR
54
+ if (scope === "files") {
55
+ const fileEls = Array.from(document.querySelectorAll("[data-tagsearch-path], [data-path], .file-header [data-path], .file-info a"));
56
+ const changedFiles = Array.from(new Set(fileEls.map((el) => el.getAttribute("data-tagsearch-path") || el.getAttribute("data-path") || el.textContent?.trim()).filter(Boolean)));
57
+ return {
58
+ ...res,
59
+ number: parseInt(idOrBranch, 10) || null,
60
+ item_title: titleEl?.textContent?.replace(/\\s+/g, " ")?.trim(),
61
+ files_count: changedFiles.length,
62
+ files: changedFiles,
63
+ };
64
+ }
65
+
66
+ const maxComments = scope === "comments" ? 50 : 5;
67
+ const commentEls = Array.from(document.querySelectorAll("[data-testid='issue-comment-item'], .timeline-comment, .timeline-comment-group")).slice(0, maxComments);
68
+ const comments = commentEls.map((el) => {
69
+ const authorEl = el.querySelector("[data-testid='author-avatar-link'], a.author, .author");
70
+ const body = el.querySelector("[data-testid='comment-body'], .comment-body");
71
+ return {
72
+ author: authorEl?.textContent?.trim() || "unknown",
73
+ preview: (body?.textContent?.trim() || "").slice(0, 400),
74
+ };
75
+ });
76
+
77
+ return {
78
+ ...res,
79
+ number: parseInt(idOrBranch, 10) || null,
80
+ item_title: titleEl?.textContent?.replace(/\\s+/g, " ")?.trim(),
81
+ state: stateBadge?.textContent?.trim(),
82
+ body_preview: bodyEl?.textContent?.trim().slice(0, 1000),
83
+ comments_count: comments.length,
84
+ recent_comments: comments,
85
+ };
86
+ }
87
+
88
+ // Blob code viewer
89
+ if (section === "blob") {
90
+ const path = parts.slice(4).join("/");
91
+ const codeContainer = document.querySelector("#read-only-cursor-text-area, [data-target='react-app.embeddedData'], .blob-wrapper table, textarea[aria-label='file content']");
92
+ const rawText = codeContainer ? codeContainer.textContent : document.querySelector("article, main")?.textContent;
93
+ return {
94
+ ...res,
95
+ file_path: path,
96
+ branch: idOrBranch,
97
+ content_preview: (rawText || "").slice(0, 2000),
98
+ };
99
+ }
100
+
101
+ // Default repo root overview
102
+ const descEl = document.querySelector("p.f4, .BorderGrid-cell p, [data-testid='repo-description']");
103
+ const readmeEl = document.querySelector("#readme article, [data-testid='readme'] article");
104
+ return {
105
+ ...res,
106
+ description: descEl?.textContent?.trim() || null,
107
+ readme_preview: readmeEl?.textContent?.trim().slice(0, 800) || null,
108
+ };
109
+ } catch (e) {
110
+ return { error: "EVAL_ERROR", message: String(e) };
111
+ }
112
+ })()`;
113
+ try {
114
+ const { result } = await session.Runtime.evaluate({
115
+ expression: evalExpression,
116
+ returnByValue: true,
117
+ });
118
+ return result.value;
119
+ }
120
+ catch (err) {
121
+ return {
122
+ error: "CDP_EVALUATION_FAILED",
123
+ message: err?.message || "Failed to inspect active tab DOM",
124
+ };
125
+ }
126
+ }
package/dist/analyze.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import { getRepoContext, isGithubError } from "./repoContext.js";
2
2
  import { loadRepoCache, saveRepoCache } from "./cache.js";
3
- import { canMakeApiCall, handleApiResponse } from "./rateLimiter.js";
3
+ import { canMakeApiCall, handleApiResponse, getApiHeaders } from "./rateLimiter.js";
4
4
  const README_CANDIDATES = ["README.md", "README", "readme.md", "Readme.md", "README.txt"];
5
- const API_HEADERS = {
6
- Accept: "application/vnd.github.v3+json",
7
- "User-Agent": "Weavetab-GitHub-Plugin",
8
- };
9
5
  /**
10
6
  * `analyze` action — repository metadata, recursive default-branch file tree and
11
7
  * a README snippet. Results are cached in the plugin's isolated storage.
@@ -21,9 +17,10 @@ export async function githubAnalyze(ctx, repoSlug) {
21
17
  const rateLimitCheck = canMakeApiCall();
22
18
  if (isGithubError(rateLimitCheck))
23
19
  return rateLimitCheck;
20
+ const apiHeaders = getApiHeaders(ctx.token);
24
21
  // 1. Repo metadata
25
22
  const repoRes = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
26
- headers: API_HEADERS,
23
+ headers: apiHeaders,
27
24
  });
28
25
  const handledRepoRes = handleApiResponse(repoRes);
29
26
  if (isGithubError(handledRepoRes))
@@ -46,7 +43,7 @@ export async function githubAnalyze(ctx, repoSlug) {
46
43
  if (isGithubError(rateLimitCheck2))
47
44
  return rateLimitCheck2;
48
45
  let file_tree = [];
49
- const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${default_branch}?recursive=1`, { headers: API_HEADERS });
46
+ const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${default_branch}?recursive=1`, { headers: apiHeaders });
50
47
  const handledTreeRes = handleApiResponse(treeRes);
51
48
  if (isGithubError(handledTreeRes))
52
49
  return handledTreeRes;
package/dist/cache.d.ts CHANGED
@@ -4,6 +4,6 @@ import type { GithubRepoCache } from "./types.js";
4
4
  * Load a previously analyzed repository from the plugin's isolated storage.
5
5
  * Returns null when absent, malformed, or older than `ttlMs`.
6
6
  */
7
- export declare function loadRepoCache(owner: string, repo: string, store: PluginStorage, ttlMs: number): Promise<GithubRepoCache | null>;
7
+ export declare function loadRepoCache(owner: string, repo: string, store: PluginStorage | undefined | null, ttlMs: number): Promise<GithubRepoCache | null>;
8
8
  /** Persist an analysis result into the plugin's isolated storage. */
9
- export declare function saveRepoCache(data: GithubRepoCache, store: PluginStorage): Promise<void>;
9
+ export declare function saveRepoCache(data: GithubRepoCache, store: PluginStorage | undefined | null): Promise<void>;
package/dist/cache.js CHANGED
@@ -10,6 +10,8 @@ function cacheFile(owner, repo) {
10
10
  * Returns null when absent, malformed, or older than `ttlMs`.
11
11
  */
12
12
  export async function loadRepoCache(owner, repo, store, ttlMs) {
13
+ if (!store || typeof store.getJSON !== "function")
14
+ return null;
13
15
  const data = await store.getJSON(cacheFile(owner, repo));
14
16
  if (!data || typeof data.analyzed_at !== "number")
15
17
  return null;
@@ -19,5 +21,7 @@ export async function loadRepoCache(owner, repo, store, ttlMs) {
19
21
  }
20
22
  /** Persist an analysis result into the plugin's isolated storage. */
21
23
  export async function saveRepoCache(data, store) {
24
+ if (!store || typeof store.setJSON !== "function")
25
+ return;
22
26
  await store.setJSON(cacheFile(data.owner, data.repo), data);
23
27
  }
package/dist/diff.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { DiffStats, GithubDiffArgs, GithubError, GithubRunContext } from "./types.js";
2
+ /**
3
+ * Extracts structured diff statistics from unified diff output.
4
+ */
5
+ export declare function parseDiffStats(diffText: string): DiffStats;
6
+ /**
7
+ * `github_diff` — fetch raw git diffs or patches directly from github.com without
8
+ * consuming any GitHub REST API rate-limited quota.
9
+ */
10
+ export declare function githubDiff(ctx: GithubRunContext, args?: GithubDiffArgs): Promise<any | GithubError>;
package/dist/diff.js ADDED
@@ -0,0 +1,117 @@
1
+ import { getDetailedRepoContext, isGithubError } from "./repoContext.js";
2
+ /**
3
+ * Extracts structured diff statistics from unified diff output.
4
+ */
5
+ export function parseDiffStats(diffText) {
6
+ const files = [];
7
+ let additions = 0;
8
+ let deletions = 0;
9
+ const lines = diffText.split("\n");
10
+ for (const line of lines) {
11
+ if (line.startsWith("diff --git")) {
12
+ const match = line.match(/diff --git a\/(.+?)\s+b\/(.+)/);
13
+ if (match && match[2]) {
14
+ files.push(match[2]);
15
+ }
16
+ }
17
+ else if (line.startsWith("+") && !line.startsWith("+++")) {
18
+ additions++;
19
+ }
20
+ else if (line.startsWith("-") && !line.startsWith("---")) {
21
+ deletions++;
22
+ }
23
+ }
24
+ return {
25
+ files_changed: Array.from(new Set(files)),
26
+ additions,
27
+ deletions,
28
+ };
29
+ }
30
+ /**
31
+ * `github_diff` — fetch raw git diffs or patches directly from github.com without
32
+ * consuming any GitHub REST API rate-limited quota.
33
+ */
34
+ export async function githubDiff(ctx, args = {}) {
35
+ const context = await getDetailedRepoContext(ctx.session, args.repo);
36
+ if (isGithubError(context))
37
+ return context;
38
+ const { owner, repo } = context;
39
+ const ext = args.format === "patch" ? "patch" : "diff";
40
+ // Target resolution with tab auto-inference fallback
41
+ const pullNumber = typeof args.pull_number === "number" ? args.pull_number : context.pullNumber;
42
+ const commitSha = typeof args.commit_sha === "string" && args.commit_sha.trim()
43
+ ? args.commit_sha.trim()
44
+ : typeof pullNumber !== "number"
45
+ ? context.commitSha
46
+ : undefined;
47
+ let targetUrl;
48
+ if (typeof pullNumber === "number") {
49
+ targetUrl = `https://github.com/${owner}/${repo}/pull/${pullNumber}.${ext}`;
50
+ }
51
+ else if (commitSha) {
52
+ targetUrl = `https://github.com/${owner}/${repo}/commit/${commitSha}.${ext}`;
53
+ }
54
+ else if (args.base && args.head) {
55
+ const base = args.base.trim();
56
+ const head = args.head.trim();
57
+ targetUrl = `https://github.com/${owner}/${repo}/compare/${base}...${head}.${ext}`;
58
+ }
59
+ else {
60
+ return {
61
+ error: "MISSING_TARGET",
62
+ message: "github_diff requires 'pull_number', 'commit_sha', or 'base'+'head' (or navigate to a PR or commit in the active browser tab).",
63
+ };
64
+ }
65
+ try {
66
+ const controller = new AbortController();
67
+ const timeout = setTimeout(() => controller.abort(), 15_000);
68
+ const res = await fetch(targetUrl, {
69
+ signal: controller.signal,
70
+ headers: {
71
+ "User-Agent": "Weavetab-GitHub-Plugin",
72
+ },
73
+ });
74
+ clearTimeout(timeout);
75
+ if (res.status === 404) {
76
+ return {
77
+ error: "NOT_FOUND",
78
+ message: `Resource not found at ${targetUrl}. Ensure the PR, commit, or compare range exists in ${owner}/${repo}.`,
79
+ };
80
+ }
81
+ if (!res.ok) {
82
+ return {
83
+ error: "FETCH_FAILED",
84
+ status: res.status,
85
+ message: `HTTP ${res.status} while fetching diff from ${targetUrl}`,
86
+ };
87
+ }
88
+ const text = await res.text();
89
+ const lines = text.split("\n");
90
+ const total_lines = lines.length;
91
+ let content = text;
92
+ let truncated = false;
93
+ // Safeguard for very large diffs
94
+ const maxLines = ctx.maxReadLines * 4;
95
+ if (total_lines > maxLines) {
96
+ content = lines.slice(0, maxLines).join("\n");
97
+ truncated = true;
98
+ }
99
+ const stats = parseDiffStats(text);
100
+ return {
101
+ owner,
102
+ repo,
103
+ target_url: targetUrl,
104
+ format: ext,
105
+ total_lines,
106
+ truncated,
107
+ stats,
108
+ diff: content,
109
+ };
110
+ }
111
+ catch (err) {
112
+ return {
113
+ error: "FETCH_ERROR",
114
+ message: err?.message || "Failed to fetch raw diff",
115
+ };
116
+ }
117
+ }
package/dist/github.d.ts CHANGED
@@ -2,10 +2,11 @@ import type { GithubError, GithubRunContext, GithubToolArgs } from "./types.js";
2
2
  import { githubAnalyze } from "./analyze.js";
3
3
  import { githubRead } from "./read.js";
4
4
  import { githubIssues } from "./issues.js";
5
- import { githubGetPr } from "./pr.js";
5
+ import { githubPullRequest, githubGetPr } from "./pr.js";
6
+ import { githubDiff } from "./diff.js";
7
+ import { githubActiveTab } from "./activeTab.js";
6
8
  /**
7
- * Single entry point for the `github` MCP tool. Dispatches to the matching
8
- * action handler and funnels every result through a plain serializable value.
9
+ * Backwards-compatible legacy entry point for single-tool callers.
9
10
  */
10
11
  export declare function runGithub(ctx: GithubRunContext, args: GithubToolArgs): Promise<any | GithubError>;
11
- export { githubAnalyze, githubRead, githubIssues, githubGetPr };
12
+ export { githubAnalyze, githubRead, githubIssues, githubPullRequest, githubGetPr, githubDiff, githubActiveTab, };
package/dist/github.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { githubAnalyze } from "./analyze.js";
2
2
  import { githubRead } from "./read.js";
3
3
  import { githubIssues } from "./issues.js";
4
- import { githubGetPr } from "./pr.js";
4
+ import { githubPullRequest, githubGetPr } from "./pr.js";
5
+ import { githubDiff } from "./diff.js";
6
+ import { githubActiveTab } from "./activeTab.js";
5
7
  /**
6
- * Single entry point for the `github` MCP tool. Dispatches to the matching
7
- * action handler and funnels every result through a plain serializable value.
8
+ * Backwards-compatible legacy entry point for single-tool callers.
8
9
  */
9
10
  export async function runGithub(ctx, args) {
10
11
  switch (args.action) {
@@ -18,7 +19,7 @@ export async function runGithub(ctx, args) {
18
19
  message: "action 'read' requires a 'paths' array with at least one file path.",
19
20
  };
20
21
  }
21
- return githubRead(ctx, { repo: args.repo, paths, line_range: args.line_range });
22
+ return githubRead(ctx, { repo: args.repo, paths, branch: args.branch, line_range: args.line_range });
22
23
  }
23
24
  case "issues":
24
25
  return githubIssues(ctx, {
@@ -30,7 +31,7 @@ export async function runGithub(ctx, args) {
30
31
  limit: args.limit,
31
32
  });
32
33
  case "pr":
33
- return githubGetPr(ctx, { repo: args.repo, number: args.number, state: args.state, limit: args.limit });
34
+ return githubPullRequest(ctx, { repo: args.repo, number: args.number, state: args.state, limit: args.limit });
34
35
  default:
35
36
  return {
36
37
  error: "INVALID_ACTION",
@@ -38,4 +39,4 @@ export async function runGithub(ctx, args) {
38
39
  };
39
40
  }
40
41
  }
41
- export { githubAnalyze, githubRead, githubIssues, githubGetPr };
42
+ export { githubAnalyze, githubRead, githubIssues, githubPullRequest, githubGetPr, githubDiff, githubActiveTab, };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * @weavetab/plugin-github
3
- * Official Weavetab GitHub plugin — repository analysis, raw file reads, issue
4
- * search/inspection and pull request inspection over the GitHub REST API.
3
+ * Official Weavetab GitHub plugin — modular GitHub tools with zero-API options.
5
4
  *
6
- * All GitHub data is fetched over HTTP, so no browser turns are consumed and the
7
- * active tab is never navigated or mutated.
5
+ * All GitHub data is fetched over HTTP or directly via active tab DOM evaluation,
6
+ * so browser turns and rate-limited API quotas are minimized.
8
7
  *
9
8
  * Author: fy2ne
10
9
  * License: MIT
11
10
  */
12
11
  export * from "./types.js";
13
- export { runGithub } from "./github.js";
12
+ export * from "./github.js";
14
13
  export declare const githubPlugin: import("@weavetab/sdk").WeavetabPluginDefinition;
15
14
  export default githubPlugin;
16
15
  export declare const plugin: import("@weavetab/sdk").WeavetabPluginDefinition;