@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.
- package/AGENTS.md +48 -0
- package/ARCHITECTURE.md +590 -0
- package/LICENSE +631 -0
- package/README.md +909 -0
- package/SECURITY.md +196 -0
- package/TROUBLESHOOTING.md +365 -0
- package/dist/anticaptcha/detector.d.ts +49 -0
- package/dist/anticaptcha/detector.js +465 -0
- package/dist/anticaptcha/integration.d.ts +36 -0
- package/dist/anticaptcha/integration.js +123 -0
- package/dist/anticaptcha/solver.d.ts +27 -0
- package/dist/anticaptcha/solver.js +552 -0
- package/dist/audit/logger.d.ts +13 -0
- package/dist/audit/logger.js +177 -0
- package/dist/audit/telemetry.d.ts +36 -0
- package/dist/audit/telemetry.js +162 -0
- package/dist/cdp/bridge.d.ts +16 -0
- package/dist/cdp/bridge.js +826 -0
- package/dist/cdp/confirm.d.ts +26 -0
- package/dist/cdp/confirm.js +79 -0
- package/dist/cdp/connector.d.ts +37 -0
- package/dist/cdp/connector.js +661 -0
- package/dist/cdp/extension.d.ts +36 -0
- package/dist/cdp/extension.js +118 -0
- package/dist/cdp/finder.d.ts +1 -0
- package/dist/cdp/finder.js +2 -0
- package/dist/cdp/helpers.d.ts +11 -0
- package/dist/cdp/helpers.js +195 -0
- package/dist/cdp/label_cache.d.ts +12 -0
- package/dist/cdp/label_cache.js +51 -0
- package/dist/cdp/lock.d.ts +33 -0
- package/dist/cdp/lock.js +206 -0
- package/dist/cdp/omni_broker.d.ts +17 -0
- package/dist/cdp/omni_broker.js +119 -0
- package/dist/cdp/profiles.d.ts +1 -0
- package/dist/cdp/profiles.js +87 -0
- package/dist/cdp/pruner.d.ts +41 -0
- package/dist/cdp/pruner.js +263 -0
- package/dist/cdp/ref_cache.d.ts +101 -0
- package/dist/cdp/ref_cache.js +234 -0
- package/dist/cdp/retry.d.ts +5 -0
- package/dist/cdp/retry.js +39 -0
- package/dist/cdp/retry_enhanced.d.ts +44 -0
- package/dist/cdp/retry_enhanced.js +102 -0
- package/dist/cdp/security.d.ts +50 -0
- package/dist/cdp/security.js +237 -0
- package/dist/cdp/verify.d.ts +39 -0
- package/dist/cdp/verify.js +143 -0
- package/dist/cdp/walker.d.ts +91 -0
- package/dist/cdp/walker.js +896 -0
- package/dist/cdp/walker.test.d.ts +2 -0
- package/dist/cdp/walker.test.js +172 -0
- package/dist/cdp/websocket_transport.d.ts +15 -0
- package/dist/cdp/websocket_transport.js +118 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.js +1134 -0
- package/dist/cli/updater.d.ts +1 -0
- package/dist/cli/updater.js +72 -0
- package/dist/config/loader.d.ts +120 -0
- package/dist/config/loader.js +704 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1248 -0
- package/dist/intelligence/hints.d.ts +11 -0
- package/dist/intelligence/hints.js +39 -0
- package/dist/intelligence/intent.d.ts +33 -0
- package/dist/intelligence/intent.js +171 -0
- package/dist/intelligence/sessionMemory.d.ts +66 -0
- package/dist/intelligence/sessionMemory.js +284 -0
- package/dist/intelligence/sessionMemory.test.d.ts +2 -0
- package/dist/intelligence/sessionMemory.test.js +62 -0
- package/dist/intelligence/trail.d.ts +64 -0
- package/dist/intelligence/trail.js +233 -0
- package/dist/intelligence/trail.test.d.ts +2 -0
- package/dist/intelligence/trail.test.js +110 -0
- package/dist/intelligence/vision.d.ts +24 -0
- package/dist/intelligence/vision.js +151 -0
- package/dist/lifecycle/mission_manager.d.ts +16 -0
- package/dist/lifecycle/mission_manager.js +44 -0
- package/dist/overlay/bubble.d.ts +16 -0
- package/dist/overlay/bubble.js +37 -0
- package/dist/overlay/ghost.d.ts +35 -0
- package/dist/overlay/ghost.js +508 -0
- package/dist/overlay/injector.d.ts +22 -0
- package/dist/overlay/injector.js +198 -0
- package/dist/plugin/interface.d.ts +134 -0
- package/dist/plugin/interface.js +1 -0
- package/dist/plugin/loader.d.ts +18 -0
- package/dist/plugin/loader.js +155 -0
- package/dist/plugin/registry.d.ts +50 -0
- package/dist/plugin/registry.js +234 -0
- package/dist/security/blacklist.d.ts +24 -0
- package/dist/security/blacklist.js +80 -0
- package/dist/security/filter.d.ts +2 -0
- package/dist/security/filter.js +20 -0
- package/dist/security/guard.d.ts +8 -0
- package/dist/security/guard.js +35 -0
- package/dist/security/ratelimit.d.ts +11 -0
- package/dist/security/ratelimit.js +57 -0
- package/dist/security/secrets.d.ts +75 -0
- package/dist/security/secrets.js +365 -0
- package/dist/sensors/actionability.d.ts +81 -0
- package/dist/sensors/actionability.js +303 -0
- package/dist/sensors/guard.d.ts +25 -0
- package/dist/sensors/guard.js +168 -0
- package/dist/sensors/mutations.d.ts +23 -0
- package/dist/sensors/mutations.js +93 -0
- package/dist/sensors/network.d.ts +67 -0
- package/dist/sensors/network.js +193 -0
- package/dist/sensors/operation_monitor.d.ts +80 -0
- package/dist/sensors/operation_monitor.js +203 -0
- package/dist/sensors/pacing.d.ts +44 -0
- package/dist/sensors/pacing.js +111 -0
- package/dist/sensors/thoughts.d.ts +2 -0
- package/dist/sensors/thoughts.js +59 -0
- package/dist/sensors/wait.d.ts +12 -0
- package/dist/sensors/wait.js +44 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1687 -0
- package/dist/state/agent.d.ts +36 -0
- package/dist/state/agent.js +101 -0
- package/dist/state/cache.d.ts +16 -0
- package/dist/state/cache.js +92 -0
- package/dist/state/cache.test.d.ts +2 -0
- package/dist/state/cache.test.js +76 -0
- package/dist/state/delta.d.ts +71 -0
- package/dist/state/delta.js +234 -0
- package/dist/state/delta.test.d.ts +2 -0
- package/dist/state/delta.test.js +279 -0
- package/dist/state/groups.d.ts +15 -0
- package/dist/state/groups.js +80 -0
- package/dist/state/session.d.ts +30 -0
- package/dist/state/session.js +155 -0
- package/dist/tools/accessibility.d.ts +14 -0
- package/dist/tools/accessibility.js +32 -0
- package/dist/tools/ask.d.ts +14 -0
- package/dist/tools/ask.js +49 -0
- package/dist/tools/automation.d.ts +36 -0
- package/dist/tools/automation.js +218 -0
- package/dist/tools/browser_detect.d.ts +13 -0
- package/dist/tools/browser_detect.js +73 -0
- package/dist/tools/burst.d.ts +35 -0
- package/dist/tools/burst.js +344 -0
- package/dist/tools/canvas.d.ts +25 -0
- package/dist/tools/canvas.js +132 -0
- package/dist/tools/captcha.d.ts +38 -0
- package/dist/tools/captcha.js +209 -0
- package/dist/tools/click.d.ts +36 -0
- package/dist/tools/click.js +561 -0
- package/dist/tools/click_and_wait.d.ts +33 -0
- package/dist/tools/click_and_wait.js +149 -0
- package/dist/tools/clipboard.d.ts +29 -0
- package/dist/tools/clipboard.js +279 -0
- package/dist/tools/console.d.ts +21 -0
- package/dist/tools/console.js +107 -0
- package/dist/tools/cookies.d.ts +67 -0
- package/dist/tools/cookies.js +121 -0
- package/dist/tools/design_extract.d.ts +109 -0
- package/dist/tools/design_extract.js +641 -0
- package/dist/tools/dialog.d.ts +24 -0
- package/dist/tools/dialog.js +109 -0
- package/dist/tools/drag.d.ts +15 -0
- package/dist/tools/drag.js +29 -0
- package/dist/tools/emulation.d.ts +33 -0
- package/dist/tools/emulation.js +69 -0
- package/dist/tools/eval.d.ts +20 -0
- package/dist/tools/eval.js +92 -0
- package/dist/tools/fill.d.ts +41 -0
- package/dist/tools/fill.js +667 -0
- package/dist/tools/find.d.ts +30 -0
- package/dist/tools/find.js +60 -0
- package/dist/tools/geo.d.ts +19 -0
- package/dist/tools/geo.js +29 -0
- package/dist/tools/github/analyze.d.ts +4 -0
- package/dist/tools/github/analyze.js +113 -0
- package/dist/tools/github/cache.d.ts +21 -0
- package/dist/tools/github/cache.js +28 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/issues.d.ts +9 -0
- package/dist/tools/github/issues.js +88 -0
- package/dist/tools/github/pr.d.ts +5 -0
- package/dist/tools/github/pr.js +88 -0
- package/dist/tools/github/rateLimiter.d.ts +4 -0
- package/dist/tools/github/rateLimiter.js +46 -0
- package/dist/tools/github/read.d.ts +34 -0
- package/dist/tools/github/read.js +74 -0
- package/dist/tools/github/repoContext.d.ts +20 -0
- package/dist/tools/github/repoContext.js +129 -0
- package/dist/tools/highlight.d.ts +20 -0
- package/dist/tools/highlight.js +140 -0
- package/dist/tools/hover.d.ts +19 -0
- package/dist/tools/hover.js +57 -0
- package/dist/tools/inspect.d.ts +17 -0
- package/dist/tools/inspect.js +125 -0
- package/dist/tools/integration.test.d.ts +2 -0
- package/dist/tools/integration.test.js +96 -0
- package/dist/tools/key.d.ts +25 -0
- package/dist/tools/key.js +173 -0
- package/dist/tools/macro_compiler.d.ts +7 -0
- package/dist/tools/macro_compiler.js +82 -0
- package/dist/tools/mouse.d.ts +21 -0
- package/dist/tools/mouse.js +62 -0
- package/dist/tools/navigate.d.ts +16 -0
- package/dist/tools/navigate.js +188 -0
- package/dist/tools/network_intercept.d.ts +17 -0
- package/dist/tools/network_intercept.js +151 -0
- package/dist/tools/office/index.d.ts +9 -0
- package/dist/tools/office/index.js +7 -0
- package/dist/tools/office/list.d.ts +15 -0
- package/dist/tools/office/list.js +41 -0
- package/dist/tools/office/parse.d.ts +20 -0
- package/dist/tools/office/parse.js +172 -0
- package/dist/tools/pdf.d.ts +22 -0
- package/dist/tools/pdf.js +42 -0
- package/dist/tools/performance.d.ts +12 -0
- package/dist/tools/performance.js +47 -0
- package/dist/tools/performance_test.d.ts +70 -0
- package/dist/tools/performance_test.js +128 -0
- package/dist/tools/plan.d.ts +39 -0
- package/dist/tools/plan.js +161 -0
- package/dist/tools/pointer.d.ts +35 -0
- package/dist/tools/pointer.js +522 -0
- package/dist/tools/read.d.ts +30 -0
- package/dist/tools/read.js +488 -0
- package/dist/tools/recording.d.ts +45 -0
- package/dist/tools/recording.js +124 -0
- package/dist/tools/response.d.ts +129 -0
- package/dist/tools/response.js +5 -0
- package/dist/tools/scrape.d.ts +24 -0
- package/dist/tools/scrape.js +140 -0
- package/dist/tools/screenshot.d.ts +40 -0
- package/dist/tools/screenshot.js +79 -0
- package/dist/tools/scroll.d.ts +23 -0
- package/dist/tools/scroll.js +196 -0
- package/dist/tools/select.d.ts +13 -0
- package/dist/tools/select.js +217 -0
- package/dist/tools/snapshot.d.ts +27 -0
- package/dist/tools/snapshot.js +99 -0
- package/dist/tools/storage.d.ts +18 -0
- package/dist/tools/storage.js +29 -0
- package/dist/tools/tabs.d.ts +77 -0
- package/dist/tools/tabs.js +342 -0
- package/dist/tools/thought.d.ts +16 -0
- package/dist/tools/thought.js +79 -0
- package/dist/tools/type.d.ts +65 -0
- package/dist/tools/type.js +1061 -0
- package/dist/tools/type_secret.d.ts +23 -0
- package/dist/tools/type_secret.js +229 -0
- package/dist/tools/upload.d.ts +19 -0
- package/dist/tools/upload.js +103 -0
- package/dist/tools/video_compiler.d.ts +24 -0
- package/dist/tools/video_compiler.js +199 -0
- package/dist/tools/viewport.d.ts +44 -0
- package/dist/tools/viewport.js +230 -0
- package/dist/tools/wait.d.ts +19 -0
- package/dist/tools/wait.js +202 -0
- package/dist/tools/wait_for.d.ts +23 -0
- package/dist/tools/wait_for.js +30 -0
- package/dist/tools/windows.d.ts +22 -0
- package/dist/tools/windows.js +57 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +12 -0
- package/docs/DEV_MODE.md +68 -0
- package/docs/DOCUMENTATION.md +532 -0
- package/docs/PLUGINS.md +538 -0
- package/docs/TOOLS.md +104 -0
- package/docs/dev_plugin.md +310 -0
- package/ext-dist/background.js +467 -0
- package/ext-dist/content.js +3089 -0
- package/ext-dist/dashboard.html +583 -0
- package/ext-dist/hud/hud.js +73 -0
- package/ext-dist/hud/index.html +20 -0
- package/ext-dist/manifest.json +77 -0
- package/ext-dist/sidepanel/sidepanel.js +25 -0
- package/ext-dist/sidepanel.html +547 -0
- package/ext-dist/styles.css +218 -0
- package/icon.png +0 -0
- package/mcp.json +796 -0
- package/package.json +113 -0
- package/server.json +23 -0
- 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,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,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
|
+
}>;
|