@weavetab/mcp 2.5.0-beta.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.
Files changed (281) hide show
  1. package/AGENTS.md +48 -0
  2. package/ARCHITECTURE.md +590 -0
  3. package/LICENSE +631 -0
  4. package/README.md +909 -0
  5. package/SECURITY.md +196 -0
  6. package/TROUBLESHOOTING.md +365 -0
  7. package/dist/anticaptcha/detector.d.ts +49 -0
  8. package/dist/anticaptcha/detector.js +465 -0
  9. package/dist/anticaptcha/integration.d.ts +36 -0
  10. package/dist/anticaptcha/integration.js +123 -0
  11. package/dist/anticaptcha/solver.d.ts +27 -0
  12. package/dist/anticaptcha/solver.js +552 -0
  13. package/dist/audit/logger.d.ts +13 -0
  14. package/dist/audit/logger.js +177 -0
  15. package/dist/audit/telemetry.d.ts +36 -0
  16. package/dist/audit/telemetry.js +162 -0
  17. package/dist/cdp/bridge.d.ts +16 -0
  18. package/dist/cdp/bridge.js +826 -0
  19. package/dist/cdp/confirm.d.ts +26 -0
  20. package/dist/cdp/confirm.js +79 -0
  21. package/dist/cdp/connector.d.ts +37 -0
  22. package/dist/cdp/connector.js +661 -0
  23. package/dist/cdp/extension.d.ts +36 -0
  24. package/dist/cdp/extension.js +118 -0
  25. package/dist/cdp/finder.d.ts +1 -0
  26. package/dist/cdp/finder.js +2 -0
  27. package/dist/cdp/helpers.d.ts +11 -0
  28. package/dist/cdp/helpers.js +195 -0
  29. package/dist/cdp/label_cache.d.ts +12 -0
  30. package/dist/cdp/label_cache.js +51 -0
  31. package/dist/cdp/lock.d.ts +33 -0
  32. package/dist/cdp/lock.js +206 -0
  33. package/dist/cdp/omni_broker.d.ts +17 -0
  34. package/dist/cdp/omni_broker.js +119 -0
  35. package/dist/cdp/profiles.d.ts +1 -0
  36. package/dist/cdp/profiles.js +87 -0
  37. package/dist/cdp/pruner.d.ts +41 -0
  38. package/dist/cdp/pruner.js +263 -0
  39. package/dist/cdp/ref_cache.d.ts +101 -0
  40. package/dist/cdp/ref_cache.js +234 -0
  41. package/dist/cdp/retry.d.ts +5 -0
  42. package/dist/cdp/retry.js +39 -0
  43. package/dist/cdp/retry_enhanced.d.ts +44 -0
  44. package/dist/cdp/retry_enhanced.js +102 -0
  45. package/dist/cdp/security.d.ts +50 -0
  46. package/dist/cdp/security.js +237 -0
  47. package/dist/cdp/verify.d.ts +39 -0
  48. package/dist/cdp/verify.js +143 -0
  49. package/dist/cdp/walker.d.ts +91 -0
  50. package/dist/cdp/walker.js +896 -0
  51. package/dist/cdp/walker.test.d.ts +2 -0
  52. package/dist/cdp/walker.test.js +172 -0
  53. package/dist/cdp/websocket_transport.d.ts +15 -0
  54. package/dist/cdp/websocket_transport.js +118 -0
  55. package/dist/cli/plugin.d.ts +9 -0
  56. package/dist/cli/plugin.js +1134 -0
  57. package/dist/cli/updater.d.ts +1 -0
  58. package/dist/cli/updater.js +72 -0
  59. package/dist/config/loader.d.ts +120 -0
  60. package/dist/config/loader.js +704 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +1248 -0
  63. package/dist/intelligence/hints.d.ts +11 -0
  64. package/dist/intelligence/hints.js +39 -0
  65. package/dist/intelligence/intent.d.ts +33 -0
  66. package/dist/intelligence/intent.js +171 -0
  67. package/dist/intelligence/sessionMemory.d.ts +66 -0
  68. package/dist/intelligence/sessionMemory.js +284 -0
  69. package/dist/intelligence/sessionMemory.test.d.ts +2 -0
  70. package/dist/intelligence/sessionMemory.test.js +62 -0
  71. package/dist/intelligence/trail.d.ts +64 -0
  72. package/dist/intelligence/trail.js +233 -0
  73. package/dist/intelligence/trail.test.d.ts +2 -0
  74. package/dist/intelligence/trail.test.js +110 -0
  75. package/dist/intelligence/vision.d.ts +24 -0
  76. package/dist/intelligence/vision.js +151 -0
  77. package/dist/lifecycle/mission_manager.d.ts +16 -0
  78. package/dist/lifecycle/mission_manager.js +44 -0
  79. package/dist/overlay/bubble.d.ts +16 -0
  80. package/dist/overlay/bubble.js +37 -0
  81. package/dist/overlay/ghost.d.ts +35 -0
  82. package/dist/overlay/ghost.js +508 -0
  83. package/dist/overlay/injector.d.ts +22 -0
  84. package/dist/overlay/injector.js +198 -0
  85. package/dist/plugin/interface.d.ts +134 -0
  86. package/dist/plugin/interface.js +1 -0
  87. package/dist/plugin/loader.d.ts +18 -0
  88. package/dist/plugin/loader.js +155 -0
  89. package/dist/plugin/registry.d.ts +50 -0
  90. package/dist/plugin/registry.js +234 -0
  91. package/dist/security/blacklist.d.ts +24 -0
  92. package/dist/security/blacklist.js +80 -0
  93. package/dist/security/filter.d.ts +2 -0
  94. package/dist/security/filter.js +20 -0
  95. package/dist/security/guard.d.ts +8 -0
  96. package/dist/security/guard.js +35 -0
  97. package/dist/security/ratelimit.d.ts +11 -0
  98. package/dist/security/ratelimit.js +57 -0
  99. package/dist/security/secrets.d.ts +75 -0
  100. package/dist/security/secrets.js +365 -0
  101. package/dist/sensors/actionability.d.ts +81 -0
  102. package/dist/sensors/actionability.js +303 -0
  103. package/dist/sensors/guard.d.ts +25 -0
  104. package/dist/sensors/guard.js +168 -0
  105. package/dist/sensors/mutations.d.ts +23 -0
  106. package/dist/sensors/mutations.js +93 -0
  107. package/dist/sensors/network.d.ts +67 -0
  108. package/dist/sensors/network.js +193 -0
  109. package/dist/sensors/operation_monitor.d.ts +80 -0
  110. package/dist/sensors/operation_monitor.js +203 -0
  111. package/dist/sensors/pacing.d.ts +44 -0
  112. package/dist/sensors/pacing.js +111 -0
  113. package/dist/sensors/thoughts.d.ts +2 -0
  114. package/dist/sensors/thoughts.js +59 -0
  115. package/dist/sensors/wait.d.ts +12 -0
  116. package/dist/sensors/wait.js +44 -0
  117. package/dist/server.d.ts +7 -0
  118. package/dist/server.js +1687 -0
  119. package/dist/state/agent.d.ts +36 -0
  120. package/dist/state/agent.js +101 -0
  121. package/dist/state/cache.d.ts +16 -0
  122. package/dist/state/cache.js +92 -0
  123. package/dist/state/cache.test.d.ts +2 -0
  124. package/dist/state/cache.test.js +76 -0
  125. package/dist/state/delta.d.ts +71 -0
  126. package/dist/state/delta.js +234 -0
  127. package/dist/state/delta.test.d.ts +2 -0
  128. package/dist/state/delta.test.js +279 -0
  129. package/dist/state/groups.d.ts +15 -0
  130. package/dist/state/groups.js +80 -0
  131. package/dist/state/session.d.ts +30 -0
  132. package/dist/state/session.js +155 -0
  133. package/dist/tools/accessibility.d.ts +14 -0
  134. package/dist/tools/accessibility.js +32 -0
  135. package/dist/tools/ask.d.ts +14 -0
  136. package/dist/tools/ask.js +49 -0
  137. package/dist/tools/automation.d.ts +36 -0
  138. package/dist/tools/automation.js +218 -0
  139. package/dist/tools/browser_detect.d.ts +13 -0
  140. package/dist/tools/browser_detect.js +73 -0
  141. package/dist/tools/burst.d.ts +35 -0
  142. package/dist/tools/burst.js +344 -0
  143. package/dist/tools/canvas.d.ts +25 -0
  144. package/dist/tools/canvas.js +132 -0
  145. package/dist/tools/captcha.d.ts +38 -0
  146. package/dist/tools/captcha.js +209 -0
  147. package/dist/tools/click.d.ts +36 -0
  148. package/dist/tools/click.js +561 -0
  149. package/dist/tools/click_and_wait.d.ts +33 -0
  150. package/dist/tools/click_and_wait.js +149 -0
  151. package/dist/tools/clipboard.d.ts +29 -0
  152. package/dist/tools/clipboard.js +279 -0
  153. package/dist/tools/console.d.ts +21 -0
  154. package/dist/tools/console.js +107 -0
  155. package/dist/tools/cookies.d.ts +67 -0
  156. package/dist/tools/cookies.js +121 -0
  157. package/dist/tools/design_extract.d.ts +109 -0
  158. package/dist/tools/design_extract.js +641 -0
  159. package/dist/tools/dialog.d.ts +24 -0
  160. package/dist/tools/dialog.js +109 -0
  161. package/dist/tools/drag.d.ts +15 -0
  162. package/dist/tools/drag.js +29 -0
  163. package/dist/tools/emulation.d.ts +33 -0
  164. package/dist/tools/emulation.js +69 -0
  165. package/dist/tools/eval.d.ts +20 -0
  166. package/dist/tools/eval.js +92 -0
  167. package/dist/tools/fill.d.ts +41 -0
  168. package/dist/tools/fill.js +667 -0
  169. package/dist/tools/find.d.ts +30 -0
  170. package/dist/tools/find.js +60 -0
  171. package/dist/tools/geo.d.ts +19 -0
  172. package/dist/tools/geo.js +29 -0
  173. package/dist/tools/github/analyze.d.ts +4 -0
  174. package/dist/tools/github/analyze.js +113 -0
  175. package/dist/tools/github/cache.d.ts +21 -0
  176. package/dist/tools/github/cache.js +28 -0
  177. package/dist/tools/github/index.d.ts +4 -0
  178. package/dist/tools/github/index.js +4 -0
  179. package/dist/tools/github/issues.d.ts +9 -0
  180. package/dist/tools/github/issues.js +88 -0
  181. package/dist/tools/github/pr.d.ts +5 -0
  182. package/dist/tools/github/pr.js +88 -0
  183. package/dist/tools/github/rateLimiter.d.ts +4 -0
  184. package/dist/tools/github/rateLimiter.js +46 -0
  185. package/dist/tools/github/read.d.ts +34 -0
  186. package/dist/tools/github/read.js +74 -0
  187. package/dist/tools/github/repoContext.d.ts +20 -0
  188. package/dist/tools/github/repoContext.js +129 -0
  189. package/dist/tools/highlight.d.ts +20 -0
  190. package/dist/tools/highlight.js +140 -0
  191. package/dist/tools/hover.d.ts +19 -0
  192. package/dist/tools/hover.js +57 -0
  193. package/dist/tools/inspect.d.ts +17 -0
  194. package/dist/tools/inspect.js +125 -0
  195. package/dist/tools/integration.test.d.ts +2 -0
  196. package/dist/tools/integration.test.js +96 -0
  197. package/dist/tools/key.d.ts +25 -0
  198. package/dist/tools/key.js +173 -0
  199. package/dist/tools/macro_compiler.d.ts +7 -0
  200. package/dist/tools/macro_compiler.js +82 -0
  201. package/dist/tools/mouse.d.ts +21 -0
  202. package/dist/tools/mouse.js +62 -0
  203. package/dist/tools/navigate.d.ts +16 -0
  204. package/dist/tools/navigate.js +188 -0
  205. package/dist/tools/network_intercept.d.ts +17 -0
  206. package/dist/tools/network_intercept.js +151 -0
  207. package/dist/tools/office/index.d.ts +9 -0
  208. package/dist/tools/office/index.js +7 -0
  209. package/dist/tools/office/list.d.ts +15 -0
  210. package/dist/tools/office/list.js +41 -0
  211. package/dist/tools/office/parse.d.ts +20 -0
  212. package/dist/tools/office/parse.js +172 -0
  213. package/dist/tools/pdf.d.ts +22 -0
  214. package/dist/tools/pdf.js +42 -0
  215. package/dist/tools/performance.d.ts +12 -0
  216. package/dist/tools/performance.js +47 -0
  217. package/dist/tools/performance_test.d.ts +70 -0
  218. package/dist/tools/performance_test.js +128 -0
  219. package/dist/tools/plan.d.ts +39 -0
  220. package/dist/tools/plan.js +161 -0
  221. package/dist/tools/pointer.d.ts +35 -0
  222. package/dist/tools/pointer.js +522 -0
  223. package/dist/tools/read.d.ts +30 -0
  224. package/dist/tools/read.js +488 -0
  225. package/dist/tools/recording.d.ts +45 -0
  226. package/dist/tools/recording.js +124 -0
  227. package/dist/tools/response.d.ts +129 -0
  228. package/dist/tools/response.js +5 -0
  229. package/dist/tools/scrape.d.ts +24 -0
  230. package/dist/tools/scrape.js +140 -0
  231. package/dist/tools/screenshot.d.ts +40 -0
  232. package/dist/tools/screenshot.js +79 -0
  233. package/dist/tools/scroll.d.ts +23 -0
  234. package/dist/tools/scroll.js +196 -0
  235. package/dist/tools/select.d.ts +13 -0
  236. package/dist/tools/select.js +217 -0
  237. package/dist/tools/snapshot.d.ts +27 -0
  238. package/dist/tools/snapshot.js +99 -0
  239. package/dist/tools/storage.d.ts +18 -0
  240. package/dist/tools/storage.js +29 -0
  241. package/dist/tools/tabs.d.ts +77 -0
  242. package/dist/tools/tabs.js +342 -0
  243. package/dist/tools/thought.d.ts +16 -0
  244. package/dist/tools/thought.js +79 -0
  245. package/dist/tools/type.d.ts +65 -0
  246. package/dist/tools/type.js +1061 -0
  247. package/dist/tools/type_secret.d.ts +23 -0
  248. package/dist/tools/type_secret.js +229 -0
  249. package/dist/tools/upload.d.ts +19 -0
  250. package/dist/tools/upload.js +103 -0
  251. package/dist/tools/video_compiler.d.ts +24 -0
  252. package/dist/tools/video_compiler.js +199 -0
  253. package/dist/tools/viewport.d.ts +44 -0
  254. package/dist/tools/viewport.js +230 -0
  255. package/dist/tools/wait.d.ts +19 -0
  256. package/dist/tools/wait.js +202 -0
  257. package/dist/tools/wait_for.d.ts +23 -0
  258. package/dist/tools/wait_for.js +30 -0
  259. package/dist/tools/windows.d.ts +22 -0
  260. package/dist/tools/windows.js +57 -0
  261. package/dist/ui/index.d.ts +1 -0
  262. package/dist/ui/index.js +12 -0
  263. package/docs/DEV_MODE.md +68 -0
  264. package/docs/DOCUMENTATION.md +532 -0
  265. package/docs/PLUGINS.md +538 -0
  266. package/docs/TOOLS.md +104 -0
  267. package/docs/dev_plugin.md +310 -0
  268. package/ext-dist/background.js +467 -0
  269. package/ext-dist/content.js +3089 -0
  270. package/ext-dist/dashboard.html +583 -0
  271. package/ext-dist/hud/hud.js +73 -0
  272. package/ext-dist/hud/index.html +20 -0
  273. package/ext-dist/manifest.json +77 -0
  274. package/ext-dist/sidepanel/sidepanel.js +25 -0
  275. package/ext-dist/sidepanel.html +547 -0
  276. package/ext-dist/styles.css +218 -0
  277. package/icon.png +0 -0
  278. package/mcp.json +796 -0
  279. package/package.json +113 -0
  280. package/server.json +23 -0
  281. package/smithery.yaml +11 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Weavetab — tools/find.ts
3
+ * browser_find: DEPRECATED — prefer browser_click with action:'find' which uses the same
4
+ * resolution chain but with memory-intercept and URC cache for 10x faster results.
5
+ * This shim is kept for backward compatibility only.
6
+ * Weavetab by fy2ne
7
+ */
8
+ import CDP from "chrome-remote-interface";
9
+ import type { Config } from "../config/loader.js";
10
+ export interface FindOptions {
11
+ query: string;
12
+ role?: string;
13
+ fuzzy?: boolean;
14
+ matchPlaceholder?: boolean;
15
+ matchTitle?: boolean;
16
+ }
17
+ export interface FindResult {
18
+ success: boolean;
19
+ found: boolean;
20
+ rect?: {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ nodeId?: number;
27
+ selector?: string;
28
+ label?: string;
29
+ }
30
+ export declare function browserFind(session: CDP.Client, options: FindOptions, _config: Config): Promise<FindResult>;
@@ -0,0 +1,60 @@
1
+ export async function browserFind(session, options, _config) {
2
+ const { query, role, fuzzy = true, matchPlaceholder = true, matchTitle = true } = options;
3
+ if (!query)
4
+ throw new Error("browser_find requires a query string");
5
+ // Find tool must search ALL elements, not just interactive ones.
6
+ const { result } = await session.Runtime.evaluate({
7
+ expression: `(function(q) {
8
+ function norm(s) { return (s || '').toLowerCase().replace(/[.\\u2026\\s]+/g, ' ').trim(); }
9
+ var qLow = norm(q);
10
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, false);
11
+ var matches = [];
12
+ while (walker.nextNode()) {
13
+ var node = walker.currentNode;
14
+ if (node.nodeType === Node.TEXT_NODE) {
15
+ var t = norm(node.textContent);
16
+ if (t && t.includes(qLow)) {
17
+ matches.push(node.parentElement);
18
+ }
19
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
20
+ var el = node;
21
+ var aria = norm(el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('placeholder') || '');
22
+ if (aria && aria.includes(qLow)) {
23
+ matches.push(el);
24
+ }
25
+ }
26
+ }
27
+ if (matches.length === 0) return null;
28
+ var match = matches[0];
29
+ var r = match.getBoundingClientRect();
30
+ var sel = '';
31
+ if (match.id) {
32
+ sel = '#' + CSS.escape(match.id);
33
+ } else {
34
+ var tmp = 'wt-tmp-' + Math.random().toString(36).slice(2,8);
35
+ match.setAttribute('data-wt-id', tmp);
36
+ sel = '[data-wt-id="' + tmp + '"]';
37
+ }
38
+ return {
39
+ label: match.getAttribute('aria-label') || (match.textContent||'').trim().slice(0,80) || q,
40
+ x: Math.round(r.left),
41
+ y: Math.round(r.top),
42
+ width: Math.round(r.width),
43
+ height: Math.round(r.height),
44
+ selector: sel
45
+ };
46
+ })(${JSON.stringify(query)})`,
47
+ returnByValue: true
48
+ }).catch(() => ({ result: { value: null } }));
49
+ if (result && result.value) {
50
+ const val = result.value;
51
+ return {
52
+ success: true,
53
+ found: true,
54
+ rect: { x: val.x, y: val.y, width: val.width, height: val.height },
55
+ selector: val.selector,
56
+ label: val.label
57
+ };
58
+ }
59
+ return { success: true, found: false };
60
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Weavetab — tools/geo.ts
3
+ * browser_geo: Override geolocation and timezone.
4
+ * Weavetab by fy2ne
5
+ */
6
+ import CDP from "chrome-remote-interface";
7
+ import type { Config } from "../config/loader.js";
8
+ export interface GeoOptions {
9
+ action: "set" | "clear";
10
+ latitude?: number;
11
+ longitude?: number;
12
+ accuracy?: number;
13
+ timezoneId?: string;
14
+ }
15
+ export declare function browserGeo(session: CDP.Client, options: GeoOptions, _config: Config): Promise<{
16
+ success: boolean;
17
+ message: string;
18
+ }>;
19
+ //# sourceMappingURL=geo.d.ts.map
@@ -0,0 +1,29 @@
1
+ export async function browserGeo(session, options, _config) {
2
+ const { action, latitude, longitude, accuracy = 100, timezoneId } = options;
3
+ if (action === "clear") {
4
+ await session.Emulation.clearGeolocationOverride();
5
+ await session.Emulation.setTimezoneOverride({ timezoneId: "" });
6
+ return { success: true, message: "Cleared geolocation and timezone overrides" };
7
+ }
8
+ if (action === "set") {
9
+ let msg = "";
10
+ if (latitude !== undefined && longitude !== undefined) {
11
+ await session.Emulation.setGeolocationOverride({
12
+ latitude,
13
+ longitude,
14
+ accuracy
15
+ });
16
+ msg += `Geolocation set to ${latitude}, ${longitude}. `;
17
+ }
18
+ if (timezoneId) {
19
+ await session.Emulation.setTimezoneOverride({ timezoneId });
20
+ msg += `Timezone set to ${timezoneId}.`;
21
+ }
22
+ if (!msg) {
23
+ throw new Error("Must provide latitude/longitude or timezoneId to set");
24
+ }
25
+ return { success: true, message: msg.trim() };
26
+ }
27
+ throw new Error("Unknown action");
28
+ }
29
+ //# sourceMappingURL=geo.js.map
@@ -0,0 +1,4 @@
1
+ import CDP from "chrome-remote-interface";
2
+ import type { Config } from "../../config/loader.js";
3
+ import { GithubRepoCache } from "./cache.js";
4
+ export declare function githubAnalyze(session: CDP.Client, config: Config, repoSlug?: string): Promise<import("./repoContext.js").GithubToolError | GithubRepoCache>;
@@ -0,0 +1,113 @@
1
+ import { getRepoContext, isGithubError } from "./repoContext.js";
2
+ import { loadRepoCache, saveRepoCache } from "./cache.js";
3
+ import { canMakeApiCall, handleApiResponse } from "./rateLimiter.js";
4
+ export async function githubAnalyze(session, config, repoSlug) {
5
+ const context = await getRepoContext(session, repoSlug);
6
+ if (isGithubError(context))
7
+ return context;
8
+ const { owner, repo } = context;
9
+ // Check cache
10
+ const cached = await loadRepoCache(owner, repo, config);
11
+ if (cached) {
12
+ return cached;
13
+ }
14
+ // Check rate limit before proceeding
15
+ const rateLimitCheck = canMakeApiCall();
16
+ if (isGithubError(rateLimitCheck))
17
+ return rateLimitCheck;
18
+ // 1. Fetch repo metadata
19
+ const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;
20
+ const repoRes = await fetch(repoUrl, {
21
+ headers: {
22
+ "Accept": "application/vnd.github.v3+json",
23
+ "User-Agent": "Weavetab-MCP"
24
+ }
25
+ });
26
+ const handledRepoRes = handleApiResponse(repoRes);
27
+ if (isGithubError(handledRepoRes))
28
+ return handledRepoRes;
29
+ if (repoRes.status === 404) {
30
+ return {
31
+ error: "REPO_NOT_FOUND",
32
+ message: "Repository not found or not accessible. Note: Only public repositories are supported."
33
+ };
34
+ }
35
+ if (!repoRes.ok) {
36
+ return {
37
+ error: "API_ERROR",
38
+ message: `GitHub API error: ${repoRes.statusText}`
39
+ };
40
+ }
41
+ const repoData = await repoRes.json();
42
+ const default_branch = repoData.default_branch || "main";
43
+ const description = repoData.description || null;
44
+ const language = repoData.language || null;
45
+ // Check rate limit again just in case
46
+ const rateLimitCheck2 = canMakeApiCall();
47
+ if (isGithubError(rateLimitCheck2))
48
+ return rateLimitCheck2;
49
+ // 2. Fetch file tree
50
+ const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${default_branch}?recursive=1`;
51
+ const treeRes = await fetch(treeUrl, {
52
+ headers: {
53
+ "Accept": "application/vnd.github.v3+json",
54
+ "User-Agent": "Weavetab-MCP"
55
+ }
56
+ });
57
+ const handledTreeRes = handleApiResponse(treeRes);
58
+ if (isGithubError(handledTreeRes))
59
+ return handledTreeRes;
60
+ let file_tree = [];
61
+ if (treeRes.ok) {
62
+ const treeData = await treeRes.json();
63
+ if (treeData.tree && Array.isArray(treeData.tree)) {
64
+ // Pick flat structure - it's more compact and easy to grep
65
+ file_tree = treeData.tree.map((t) => ({
66
+ path: t.path,
67
+ mode: t.mode,
68
+ type: t.type,
69
+ sha: t.sha,
70
+ size: t.size,
71
+ url: t.url
72
+ }));
73
+ }
74
+ }
75
+ // 3. Fetch README (raw)
76
+ let readme_snippet = "";
77
+ // Check common README paths
78
+ const possibleReadmes = ["README.md", "README", "readme.md", "Readme.md", "README.txt"];
79
+ const readmeEntry = file_tree.find(f => possibleReadmes.includes(f.path));
80
+ if (readmeEntry) {
81
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${default_branch}/${readmeEntry.path}`;
82
+ try {
83
+ const controller = new AbortController();
84
+ const timeout = setTimeout(() => controller.abort(), 10000);
85
+ const rawRes = await fetch(rawUrl, { signal: controller.signal });
86
+ clearTimeout(timeout);
87
+ if (rawRes.ok) {
88
+ const fullReadme = await rawRes.text();
89
+ // Truncate snippet to first 100 lines
90
+ const lines = fullReadme.split('\n');
91
+ readme_snippet = lines.slice(0, 100).join('\n');
92
+ if (lines.length > 100) {
93
+ readme_snippet += "\n\n... (truncated)";
94
+ }
95
+ }
96
+ }
97
+ catch {
98
+ // ignore
99
+ }
100
+ }
101
+ const cacheData = {
102
+ owner,
103
+ repo,
104
+ default_branch,
105
+ description,
106
+ language,
107
+ file_tree,
108
+ readme_snippet,
109
+ analyzed_at: Date.now()
110
+ };
111
+ await saveRepoCache(cacheData);
112
+ return cacheData;
113
+ }
@@ -0,0 +1,21 @@
1
+ import type { Config } from "../../config/loader.js";
2
+ export interface TreeEntry {
3
+ path: string;
4
+ mode: string;
5
+ type: string;
6
+ sha: string;
7
+ size?: number;
8
+ url: string;
9
+ }
10
+ export interface GithubRepoCache {
11
+ owner: string;
12
+ repo: string;
13
+ default_branch: string;
14
+ description: string | null;
15
+ language: string | null;
16
+ file_tree: TreeEntry[];
17
+ readme_snippet: string;
18
+ analyzed_at: number;
19
+ }
20
+ export declare function loadRepoCache(owner: string, repo: string, config: Config): Promise<GithubRepoCache | null>;
21
+ export declare function saveRepoCache(data: GithubRepoCache): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import { getConfigGithubCacheTtlMs } from "../../config/loader.js";
5
+ const CACHE_DIR = join(homedir(), ".weavetab-system", "github_cache");
6
+ function getCacheFile(owner, repo) {
7
+ const safeOwner = owner.replace(/[^a-zA-Z0-9.-]/g, "_");
8
+ const safeRepo = repo.replace(/[^a-zA-Z0-9.-]/g, "_");
9
+ return join(CACHE_DIR, `${safeOwner}_${safeRepo}.json`);
10
+ }
11
+ export async function loadRepoCache(owner, repo, config) {
12
+ try {
13
+ const content = await readFile(getCacheFile(owner, repo), "utf-8");
14
+ const data = JSON.parse(content);
15
+ const ttl = getConfigGithubCacheTtlMs(config) ?? (30 * 60 * 1000);
16
+ if (Date.now() - data.analyzed_at > ttl) {
17
+ return null; // Stale cache
18
+ }
19
+ return data;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export async function saveRepoCache(data) {
26
+ await mkdir(CACHE_DIR, { recursive: true });
27
+ await writeFile(getCacheFile(data.owner, data.repo), JSON.stringify(data), "utf-8");
28
+ }
@@ -0,0 +1,4 @@
1
+ export { githubAnalyze } from "./analyze.js";
2
+ export { githubRead } from "./read.js";
3
+ export { githubIssues } from "./issues.js";
4
+ export { githubGetPr } from "./pr.js";
@@ -0,0 +1,4 @@
1
+ export { githubAnalyze } from "./analyze.js";
2
+ export { githubRead } from "./read.js";
3
+ export { githubIssues } from "./issues.js";
4
+ export { githubGetPr } from "./pr.js";
@@ -0,0 +1,9 @@
1
+ import CDP from "chrome-remote-interface";
2
+ export declare function githubIssues(session: CDP.Client, args: {
3
+ repo?: string;
4
+ search?: string;
5
+ number?: number;
6
+ labels?: string[];
7
+ state?: 'open' | 'closed' | 'all';
8
+ limit?: number;
9
+ }): Promise<any>;
@@ -0,0 +1,88 @@
1
+ import { getRepoContext, isGithubError } from "./repoContext.js";
2
+ import { canMakeApiCall, handleApiResponse } from "./rateLimiter.js";
3
+ export async function githubIssues(session, args) {
4
+ const context = await getRepoContext(session, args.repo);
5
+ if (isGithubError(context))
6
+ return context;
7
+ const { owner, repo } = context;
8
+ const rateLimitCheck = canMakeApiCall();
9
+ if (isGithubError(rateLimitCheck))
10
+ return rateLimitCheck;
11
+ const headers = {
12
+ "Accept": "application/vnd.github.v3+json",
13
+ "User-Agent": "Weavetab-MCP"
14
+ };
15
+ // If number is provided, fetch specific issue + comments
16
+ if (args.number) {
17
+ const issueUrl = `https://api.github.com/repos/${owner}/${repo}/issues/${args.number}`;
18
+ const issueRes = await fetch(issueUrl, { headers });
19
+ const handledIssueRes = handleApiResponse(issueRes);
20
+ if (isGithubError(handledIssueRes))
21
+ return handledIssueRes;
22
+ if (!issueRes.ok) {
23
+ return { error: "FETCH_FAILED", status: issueRes.status, message: `Failed to fetch issue #${args.number}` };
24
+ }
25
+ const issueData = await issueRes.json();
26
+ let comments = [];
27
+ if (issueData.comments > 0) {
28
+ const rateLimitCheck2 = canMakeApiCall();
29
+ if (!isGithubError(rateLimitCheck2)) {
30
+ const commentsUrl = `https://api.github.com/repos/${owner}/${repo}/issues/${args.number}/comments`;
31
+ const commentsRes = await fetch(commentsUrl, { headers });
32
+ const handledCommentsRes = handleApiResponse(commentsRes);
33
+ if (!isGithubError(handledCommentsRes) && commentsRes.ok) {
34
+ const commentsData = await commentsRes.json();
35
+ comments = commentsData.map((c) => ({
36
+ id: c.id,
37
+ user: c.user?.login,
38
+ body: c.body,
39
+ created_at: c.created_at
40
+ }));
41
+ }
42
+ }
43
+ }
44
+ return {
45
+ number: issueData.number,
46
+ title: issueData.title,
47
+ state: issueData.state,
48
+ user: issueData.user?.login,
49
+ body: issueData.body,
50
+ created_at: issueData.created_at,
51
+ labels: issueData.labels?.map((l) => l.name),
52
+ comments
53
+ };
54
+ }
55
+ // Else list issues
56
+ let urlStr = `https://api.github.com/repos/${owner}/${repo}/issues?per_page=${args.limit || 5}`;
57
+ if (args.state)
58
+ urlStr += `&state=${args.state}`;
59
+ if (args.labels && args.labels.length > 0)
60
+ urlStr += `&labels=${args.labels.join(',')}`;
61
+ // GitHub Search API is different from Issues API. If search is provided, we should use search API.
62
+ if (args.search) {
63
+ let q = `repo:${owner}/${repo} is:issue ${args.search}`;
64
+ if (args.state && args.state !== 'all')
65
+ q += ` is:${args.state}`;
66
+ if (args.labels)
67
+ args.labels.forEach(l => q += ` label:"${l}"`);
68
+ urlStr = `https://api.github.com/search/issues?q=${encodeURIComponent(q)}&per_page=${args.limit || 5}`;
69
+ }
70
+ const listRes = await fetch(urlStr, { headers });
71
+ const handledListRes = handleApiResponse(listRes);
72
+ if (isGithubError(handledListRes))
73
+ return handledListRes;
74
+ if (!listRes.ok) {
75
+ return { error: "FETCH_FAILED", status: listRes.status, message: "Failed to list/search issues" };
76
+ }
77
+ const listData = await listRes.json();
78
+ const items = args.search ? listData.items : listData;
79
+ return items.map((i) => ({
80
+ number: i.number,
81
+ title: i.title,
82
+ state: i.state,
83
+ user: i.user?.login,
84
+ labels: i.labels?.map((l) => l.name),
85
+ created_at: i.created_at,
86
+ comments_count: i.comments
87
+ }));
88
+ }
@@ -0,0 +1,5 @@
1
+ import CDP from "chrome-remote-interface";
2
+ export declare function githubGetPr(session: CDP.Client, args: {
3
+ repo?: string;
4
+ number?: number;
5
+ }): Promise<any>;
@@ -0,0 +1,88 @@
1
+ import { getRepoContext, isGithubError } from "./repoContext.js";
2
+ import { canMakeApiCall, handleApiResponse } from "./rateLimiter.js";
3
+ export async function githubGetPr(session, args) {
4
+ const context = await getRepoContext(session, args.repo);
5
+ if (isGithubError(context))
6
+ return context;
7
+ const { owner, repo } = context;
8
+ const rateLimitCheck = canMakeApiCall();
9
+ if (isGithubError(rateLimitCheck))
10
+ return rateLimitCheck;
11
+ const headers = {
12
+ "Accept": "application/vnd.github.v3+json",
13
+ "User-Agent": "Weavetab-MCP"
14
+ };
15
+ if (args.number) {
16
+ const prUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${args.number}`;
17
+ const prRes = await fetch(prUrl, { headers });
18
+ const handledPrRes = handleApiResponse(prRes);
19
+ if (isGithubError(handledPrRes))
20
+ return handledPrRes;
21
+ if (!prRes.ok) {
22
+ return { error: "FETCH_FAILED", status: prRes.status, message: `Failed to fetch PR #${args.number}` };
23
+ }
24
+ const prData = await prRes.json();
25
+ let files_changed = [];
26
+ const rateLimitCheck2 = canMakeApiCall();
27
+ if (!isGithubError(rateLimitCheck2)) {
28
+ const filesUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${args.number}/files`;
29
+ const filesRes = await fetch(filesUrl, { headers });
30
+ const handledFilesRes = handleApiResponse(filesRes);
31
+ if (!isGithubError(handledFilesRes) && filesRes.ok) {
32
+ const filesData = await filesRes.json();
33
+ files_changed = filesData.map((f) => f.filename);
34
+ }
35
+ }
36
+ let comments = [];
37
+ if (prData.comments > 0) {
38
+ const rateLimitCheck3 = canMakeApiCall();
39
+ if (!isGithubError(rateLimitCheck3)) {
40
+ const commentsUrl = `https://api.github.com/repos/${owner}/${repo}/issues/${args.number}/comments`;
41
+ const commentsRes = await fetch(commentsUrl, { headers });
42
+ const handledCommentsRes = handleApiResponse(commentsRes);
43
+ if (!isGithubError(handledCommentsRes) && commentsRes.ok) {
44
+ const commentsData = await commentsRes.json();
45
+ comments = commentsData.map((c) => ({
46
+ id: c.id,
47
+ user: c.user?.login,
48
+ body: c.body,
49
+ created_at: c.created_at
50
+ }));
51
+ }
52
+ }
53
+ }
54
+ return {
55
+ number: prData.number,
56
+ title: prData.title,
57
+ state: prData.state,
58
+ user: prData.user?.login,
59
+ body: prData.body,
60
+ created_at: prData.created_at,
61
+ merged: prData.merged,
62
+ mergeable: prData.mergeable,
63
+ additions: prData.additions,
64
+ deletions: prData.deletions,
65
+ changed_files: prData.changed_files,
66
+ files: files_changed,
67
+ comments
68
+ };
69
+ }
70
+ // Else list recent PRs
71
+ const listUrl = `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&per_page=10`;
72
+ const listRes = await fetch(listUrl, { headers });
73
+ const handledListRes = handleApiResponse(listRes);
74
+ if (isGithubError(handledListRes))
75
+ return handledListRes;
76
+ if (!listRes.ok) {
77
+ return { error: "FETCH_FAILED", status: listRes.status, message: "Failed to list PRs" };
78
+ }
79
+ const listData = await listRes.json();
80
+ return listData.map((pr) => ({
81
+ number: pr.number,
82
+ title: pr.title,
83
+ state: pr.state,
84
+ user: pr.user?.login,
85
+ created_at: pr.created_at,
86
+ updated_at: pr.updated_at
87
+ }));
88
+ }
@@ -0,0 +1,4 @@
1
+ import type { GithubToolError } from "./repoContext.js";
2
+ export declare function canMakeApiCall(): true | GithubToolError;
3
+ export declare function recordApiCall(headers: Headers): void;
4
+ export declare function handleApiResponse(response: Response): Response | GithubToolError;
@@ -0,0 +1,46 @@
1
+ let state = {
2
+ remaining: 60,
3
+ resetAt: null,
4
+ };
5
+ const SAFETY_THRESHOLD = 5;
6
+ export function canMakeApiCall() {
7
+ const nowSec = Math.floor(Date.now() / 1000);
8
+ if (state.resetAt && nowSec >= state.resetAt) {
9
+ // We've passed the reset window, assume we have quota again
10
+ state.remaining = 60;
11
+ state.resetAt = null;
12
+ }
13
+ if (state.remaining < SAFETY_THRESHOLD) {
14
+ const resetTime = state.resetAt ? new Date(state.resetAt * 1000).toISOString() : "unknown";
15
+ return {
16
+ error: "RATE_LIMIT_LOW",
17
+ message: `GitHub API hourly rate limit is too low (remaining: ${state.remaining}). Please wait until ${resetTime} before making more API calls. Note: github_read does NOT consume API calls.`,
18
+ remaining: state.remaining,
19
+ reset_at: state.resetAt
20
+ };
21
+ }
22
+ return true;
23
+ }
24
+ export function recordApiCall(headers) {
25
+ const remainingStr = headers.get("x-ratelimit-remaining");
26
+ const resetStr = headers.get("x-ratelimit-reset");
27
+ if (remainingStr) {
28
+ state.remaining = parseInt(remainingStr, 10) || state.remaining;
29
+ }
30
+ if (resetStr) {
31
+ state.resetAt = parseInt(resetStr, 10) || state.resetAt;
32
+ }
33
+ }
34
+ export function handleApiResponse(response) {
35
+ recordApiCall(response.headers);
36
+ if (response.status === 403 || response.status === 429) {
37
+ const resetTime = state.resetAt ? new Date(state.resetAt * 1000).toISOString() : "unknown";
38
+ return {
39
+ error: "RATE_LIMIT_EXCEEDED",
40
+ message: `GitHub API rate limit exceeded. Please wait until ${resetTime}.`,
41
+ remaining: state.remaining,
42
+ reset_at: state.resetAt
43
+ };
44
+ }
45
+ return response;
46
+ }
@@ -0,0 +1,34 @@
1
+ import CDP from "chrome-remote-interface";
2
+ import type { Config } from "../../config/loader.js";
3
+ export declare function githubRead(session: CDP.Client, args: {
4
+ repo?: string;
5
+ paths: string[];
6
+ line_range?: [number, number];
7
+ }, config: Config): Promise<import("./repoContext.js").GithubToolError | {
8
+ warnings: string[] | undefined;
9
+ files: ({
10
+ path: string;
11
+ error: string;
12
+ status: number;
13
+ message: string;
14
+ total_lines?: undefined;
15
+ truncated?: undefined;
16
+ content?: undefined;
17
+ } | {
18
+ path: string;
19
+ total_lines: number;
20
+ truncated: boolean;
21
+ content: string;
22
+ error?: undefined;
23
+ status?: undefined;
24
+ message?: undefined;
25
+ } | {
26
+ path: string;
27
+ error: string;
28
+ message: any;
29
+ status?: undefined;
30
+ total_lines?: undefined;
31
+ truncated?: undefined;
32
+ content?: undefined;
33
+ })[];
34
+ }>;