@weavetab/mcp 2.5.0 → 2.5.1
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 +49 -49
- package/ARCHITECTURE.md +590 -590
- package/LICENSE +216 -216
- package/README.md +269 -269
- package/SECURITY.md +207 -207
- package/TROUBLESHOOTING.md +373 -365
- package/dist/anticaptcha/detector.js +395 -395
- package/dist/anticaptcha/solver.js +130 -130
- package/dist/cdp/bridge.js +73 -73
- package/dist/cdp/connector.js +46 -22
- package/dist/cdp/extension.js +34 -34
- package/dist/cdp/helpers.js +174 -174
- package/dist/cdp/pruner.js +195 -195
- package/dist/cdp/ref_cache.js +66 -66
- package/dist/cdp/security.js +10 -10
- package/dist/cdp/verify.js +57 -57
- package/dist/cdp/walker.js +563 -525
- package/dist/cli/plugin.js +26 -3
- package/dist/config/loader.d.ts +0 -3
- package/dist/config/loader.js +0 -9
- package/dist/index.js +0 -4
- package/dist/intelligence/intent.js +81 -81
- package/dist/intelligence/sessionMemory.js +2 -2
- package/dist/overlay/ghost.js +22 -22
- package/dist/overlay/injector.js +89 -89
- package/dist/plugin/extension_bridge.js +54 -54
- package/dist/plugin/interface.d.ts +7 -0
- package/dist/plugin/loader.js +3 -4
- package/dist/plugin/schema.d.ts +11 -0
- package/dist/plugin/schema.js +59 -0
- package/dist/security/guard.js +18 -18
- package/dist/sensors/actionability.js +167 -167
- package/dist/sensors/guard.js +29 -29
- package/dist/sensors/network.js +15 -17
- package/dist/server.d.ts +1 -1
- package/dist/server.js +173 -144
- package/dist/state/delta.js +6 -7
- package/dist/tools/automation.js +85 -85
- package/dist/tools/canvas.js +48 -25
- package/dist/tools/checkpoint.js +94 -35
- package/dist/tools/click.js +52 -41
- package/dist/tools/clipboard.js +80 -74
- package/dist/tools/design_extract.js +441 -441
- package/dist/tools/dialog.js +13 -13
- package/dist/tools/emulation.d.ts +2 -0
- package/dist/tools/emulation.js +7 -0
- package/dist/tools/fill.js +344 -347
- package/dist/tools/find.js +78 -52
- package/dist/tools/highlight.js +67 -67
- package/dist/tools/inspect.js +54 -25
- package/dist/tools/key.js +42 -35
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +35 -35
- package/dist/tools/office/parse.js +62 -18
- package/dist/tools/pdf.d.ts +9 -0
- package/dist/tools/pdf.js +7 -4
- package/dist/tools/performance.d.ts +11 -4
- package/dist/tools/performance.js +94 -42
- package/dist/tools/performance_test.d.ts +2 -2
- package/dist/tools/performance_test.js +69 -69
- package/dist/tools/pointer.js +116 -116
- package/dist/tools/read.js +99 -67
- package/dist/tools/recording.js +9 -0
- package/dist/tools/scrape.js +146 -146
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +46 -4
- package/dist/tools/scroll.d.ts +3 -1
- package/dist/tools/scroll.js +13 -15
- package/dist/tools/select.d.ts +3 -0
- package/dist/tools/select.js +131 -124
- package/dist/tools/snapshot.js +8 -6
- package/dist/tools/storage.d.ts +5 -1
- package/dist/tools/storage.js +16 -0
- package/dist/tools/tabs.d.ts +7 -5
- package/dist/tools/tabs.js +79 -26
- package/dist/tools/thought.js +8 -8
- package/dist/tools/type.js +274 -274
- package/dist/tools/type_secret.js +23 -23
- package/dist/tools/upload.js +3 -3
- package/dist/tools/video_compiler.js +9 -0
- package/dist/tools/wait.js +35 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/DOCUMENTATION.md +583 -585
- package/docs/PLUGINS.md +139 -121
- package/docs/TOOLS.md +3 -4
- package/docs/architecture/blind-injection.md +83 -83
- package/docs/architecture/overview.md +49 -49
- package/docs/architecture/security.md +36 -36
- package/docs/architecture/transport.md +37 -37
- package/docs/cli/cli-commands.md +50 -50
- package/docs/cli/cli-flags.md +28 -28
- package/docs/cli/cli-scripting.md +35 -35
- package/docs/configuration/config-locations.md +23 -23
- package/docs/configuration/configuration.md +39 -27
- package/docs/configuration/env-variables.md +19 -19
- package/docs/configuration/folders.md +62 -55
- package/docs/desktop-automation/electron-apps.md +225 -214
- package/docs/get-started/installation.md +1 -1
- package/docs/get-started/introduction.md +60 -60
- package/docs/get-started/quickstart.md +2 -2
- package/docs/get-started/setup-wizard.md +27 -27
- package/docs/intelligence/site-profiles.md +151 -151
- package/docs/tools/tools.md +582 -631
- package/docs/troubleshooting/trouble-browser.md +27 -27
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +415 -415
- package/ext-dist/content.js +2527 -2527
- package/ext-dist/dashboard.html +583 -583
- package/ext-dist/hud/hud.js +3 -3
- package/ext-dist/hud/index.html +20 -20
- package/ext-dist/manifest.json +73 -73
- package/ext-dist/styles.css +223 -223
- package/mcp.json +796 -805
- package/package.json +118 -118
- package/server.json +23 -23
- package/dist/cdp/walker.test.d.ts +0 -2
- package/dist/cdp/walker.test.js +0 -172
- package/dist/intelligence/sessionMemory.test.d.ts +0 -2
- package/dist/intelligence/sessionMemory.test.js +0 -62
- package/dist/intelligence/trail.test.d.ts +0 -2
- package/dist/intelligence/trail.test.js +0 -110
- package/dist/state/cache.test.d.ts +0 -2
- package/dist/state/cache.test.js +0 -76
- package/dist/state/delta.test.d.ts +0 -2
- package/dist/state/delta.test.js +0 -279
- package/dist/state/groups.d.ts +0 -15
- package/dist/state/groups.js +0 -80
- package/dist/tools/accessibility.d.ts +0 -14
- package/dist/tools/accessibility.js +0 -32
- package/dist/tools/ask.d.ts +0 -14
- package/dist/tools/ask.js +0 -49
- package/dist/tools/click_and_wait.d.ts +0 -33
- package/dist/tools/click_and_wait.js +0 -149
- package/dist/tools/drag.d.ts +0 -15
- package/dist/tools/drag.js +0 -29
- package/dist/tools/geo.d.ts +0 -19
- package/dist/tools/geo.js +0 -29
- package/dist/tools/github/analyze.d.ts +0 -4
- package/dist/tools/github/analyze.js +0 -113
- package/dist/tools/github/cache.d.ts +0 -21
- package/dist/tools/github/cache.js +0 -28
- package/dist/tools/github/index.d.ts +0 -19
- package/dist/tools/github/index.js +0 -40
- package/dist/tools/github/issues.d.ts +0 -9
- package/dist/tools/github/issues.js +0 -88
- package/dist/tools/github/pr.d.ts +0 -5
- package/dist/tools/github/pr.js +0 -88
- package/dist/tools/github/rateLimiter.d.ts +0 -4
- package/dist/tools/github/rateLimiter.js +0 -46
- package/dist/tools/github/read.d.ts +0 -34
- package/dist/tools/github/read.js +0 -74
- package/dist/tools/github/repoContext.d.ts +0 -20
- package/dist/tools/github/repoContext.js +0 -129
- package/dist/tools/hover.d.ts +0 -19
- package/dist/tools/hover.js +0 -57
- package/dist/tools/integration.test.d.ts +0 -2
- package/dist/tools/integration.test.js +0 -96
- package/dist/tools/macro_compiler.d.ts +0 -7
- package/dist/tools/macro_compiler.js +0 -82
- package/dist/tools/mouse.d.ts +0 -21
- package/dist/tools/mouse.js +0 -62
- package/dist/tools/response.d.ts +0 -129
- package/dist/tools/response.js +0 -5
- package/dist/tools/wait_for.d.ts +0 -23
- package/dist/tools/wait_for.js +0 -30
- package/dist/tools/windows.d.ts +0 -22
- package/dist/tools/windows.js +0 -57
|
@@ -1,4 +0,0 @@
|
|
|
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;
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}>;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { getRepoContext, isGithubError } from "./repoContext.js";
|
|
2
|
-
import { loadRepoCache } from "./cache.js";
|
|
3
|
-
import { githubAnalyze } from "./analyze.js";
|
|
4
|
-
export async function githubRead(session, args, config) {
|
|
5
|
-
const context = await getRepoContext(session, args.repo);
|
|
6
|
-
if (isGithubError(context))
|
|
7
|
-
return context;
|
|
8
|
-
const { owner, repo } = context;
|
|
9
|
-
// Check cache to get default_branch and file_tree
|
|
10
|
-
let cached = await loadRepoCache(owner, repo, config);
|
|
11
|
-
if (!cached) {
|
|
12
|
-
// We need cache to get default_branch. Call analyze first.
|
|
13
|
-
const analyzeResult = await githubAnalyze(session, config, args.repo);
|
|
14
|
-
if (isGithubError(analyzeResult)) {
|
|
15
|
-
return analyzeResult;
|
|
16
|
-
}
|
|
17
|
-
cached = analyzeResult;
|
|
18
|
-
}
|
|
19
|
-
const default_branch = cached.default_branch;
|
|
20
|
-
const file_tree = cached.file_tree;
|
|
21
|
-
const results = [];
|
|
22
|
-
const warnings = [];
|
|
23
|
-
const fetchPromises = args.paths.map(async (rawPath) => {
|
|
24
|
-
// Sanitize path
|
|
25
|
-
const path = rawPath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
26
|
-
if (path.includes('..') || path.includes('\0')) {
|
|
27
|
-
return { path: rawPath, error: "INVALID_PATH", message: "Path traversal or null bytes detected." };
|
|
28
|
-
}
|
|
29
|
-
// Check if path exists in tree
|
|
30
|
-
const exists = file_tree.some(entry => entry.path === path);
|
|
31
|
-
if (!exists) {
|
|
32
|
-
warnings.push(`Path '${path}' was not found in the cached file tree. Branch contents may differ.`);
|
|
33
|
-
}
|
|
34
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${default_branch}/${path}`;
|
|
35
|
-
try {
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
38
|
-
const res = await fetch(rawUrl, { signal: controller.signal });
|
|
39
|
-
clearTimeout(timeout);
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
return { path, error: "FETCH_FAILED", status: res.status, message: res.statusText };
|
|
42
|
-
}
|
|
43
|
-
const content = await res.text();
|
|
44
|
-
const lines = content.split('\n');
|
|
45
|
-
const total_lines = lines.length;
|
|
46
|
-
let returnedContent = content;
|
|
47
|
-
let truncated = false;
|
|
48
|
-
if (args.line_range && Array.isArray(args.line_range) && args.line_range.length === 2) {
|
|
49
|
-
const start = Math.max(0, args.line_range[0] - 1); // 1-indexed to 0-indexed
|
|
50
|
-
const end = Math.min(total_lines, args.line_range[1]);
|
|
51
|
-
returnedContent = lines.slice(start, end).join('\n');
|
|
52
|
-
truncated = (start > 0 || end < total_lines);
|
|
53
|
-
}
|
|
54
|
-
else if (total_lines > 200) {
|
|
55
|
-
returnedContent = lines.slice(0, 200).join('\n');
|
|
56
|
-
truncated = true;
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
path,
|
|
60
|
-
total_lines,
|
|
61
|
-
truncated,
|
|
62
|
-
content: returnedContent
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
return { path, error: "FETCH_ERROR", message: err.message };
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
const pathResults = await Promise.all(fetchPromises);
|
|
70
|
-
return {
|
|
71
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
72
|
-
files: pathResults
|
|
73
|
-
};
|
|
74
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import CDP from "chrome-remote-interface";
|
|
2
|
-
export interface RepoContext {
|
|
3
|
-
owner: string;
|
|
4
|
-
repo: string;
|
|
5
|
-
}
|
|
6
|
-
export type GithubToolError = {
|
|
7
|
-
error: string;
|
|
8
|
-
message: string;
|
|
9
|
-
[key: string]: any;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Resolve the current GitHub repo context.
|
|
13
|
-
*
|
|
14
|
-
* @param session - CDP session
|
|
15
|
-
* @param repoSlug - Optional "owner/repo" or full GitHub URL. When provided,
|
|
16
|
-
* the function uses it directly instead of reading the tab URL.
|
|
17
|
-
* If the browser is not already on GitHub it navigates there.
|
|
18
|
-
*/
|
|
19
|
-
export declare function getRepoContext(session: CDP.Client, repoSlug?: unknown): Promise<RepoContext | GithubToolError>;
|
|
20
|
-
export declare function isGithubError(result: any): result is GithubToolError;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse a repo slug from a string that can be:
|
|
3
|
-
* - "owner/repo"
|
|
4
|
-
* - "https://github.com/owner/repo[/anything]"
|
|
5
|
-
*
|
|
6
|
-
* Returns null (instead of throwing) for any non-string input so callers
|
|
7
|
-
* never encounter `TypeError: slug.trim is not a function`.
|
|
8
|
-
*/
|
|
9
|
-
function parseRepoSlug(slug) {
|
|
10
|
-
// ── Enterprise guard: reject any non-string at runtime ─────────────────────
|
|
11
|
-
// Zod marks `repo` as optional(string), but MCP argument dispatch can
|
|
12
|
-
// forward an object / number / null when the model omits the field in some
|
|
13
|
-
// call patterns. Checking typeof here is the only safe boundary.
|
|
14
|
-
if (typeof slug !== "string")
|
|
15
|
-
return null;
|
|
16
|
-
const trimmed = slug.trim();
|
|
17
|
-
if (!trimmed)
|
|
18
|
-
return null;
|
|
19
|
-
// Full GitHub URL
|
|
20
|
-
try {
|
|
21
|
-
const u = new URL(trimmed);
|
|
22
|
-
if (u.hostname === "github.com") {
|
|
23
|
-
const parts = u.pathname.split("/").filter(Boolean);
|
|
24
|
-
if (parts.length >= 2)
|
|
25
|
-
return { owner: parts[0], repo: parts[1] };
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch { /* not a URL — fall through to shorthand parse */ }
|
|
29
|
-
// "owner/repo" shorthand
|
|
30
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
31
|
-
if (parts.length >= 2)
|
|
32
|
-
return { owner: parts[0], repo: parts[1] };
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Resolve the current GitHub repo context.
|
|
37
|
-
*
|
|
38
|
-
* @param session - CDP session
|
|
39
|
-
* @param repoSlug - Optional "owner/repo" or full GitHub URL. When provided,
|
|
40
|
-
* the function uses it directly instead of reading the tab URL.
|
|
41
|
-
* If the browser is not already on GitHub it navigates there.
|
|
42
|
-
*/
|
|
43
|
-
export async function getRepoContext(session, repoSlug) {
|
|
44
|
-
// ── Runtime type normalisation ────────────────────────────────────────────
|
|
45
|
-
// `repoSlug` is declared optional(string) in the Zod schema, but callers
|
|
46
|
-
// such as server.ts cast `(args as any).repo`, meaning any JS value can
|
|
47
|
-
// arrive here. Normalise to string | undefined before any string ops.
|
|
48
|
-
const slugStr = typeof repoSlug === "string" && repoSlug.trim() !== ""
|
|
49
|
-
? repoSlug.trim()
|
|
50
|
-
: undefined;
|
|
51
|
-
// If a repo slug was explicitly given, prefer it over the current tab URL.
|
|
52
|
-
if (slugStr) {
|
|
53
|
-
const parsed = parseRepoSlug(slugStr);
|
|
54
|
-
if (!parsed) {
|
|
55
|
-
return {
|
|
56
|
-
error: "INVALID_REPO_SLUG",
|
|
57
|
-
message: `Cannot parse a GitHub owner/repo from "${slugStr}". Use "owner/repo" or a full github.com URL.`
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
// Check whether the browser is already on a matching GitHub page.
|
|
61
|
-
// If not, navigate there so downstream tools that read the DOM work correctly.
|
|
62
|
-
try {
|
|
63
|
-
const { result } = await session.Runtime.evaluate({
|
|
64
|
-
expression: 'window.location.href',
|
|
65
|
-
returnByValue: true,
|
|
66
|
-
});
|
|
67
|
-
const currentUrl = (typeof result.value === "string" ? result.value : "");
|
|
68
|
-
const isOnMatchingRepo = currentUrl.includes(`github.com/${parsed.owner}/${parsed.repo}`);
|
|
69
|
-
if (!isOnMatchingRepo) {
|
|
70
|
-
const targetUrl = `https://github.com/${parsed.owner}/${parsed.repo}`;
|
|
71
|
-
await session.Page.navigate({ url: targetUrl });
|
|
72
|
-
// Wait briefly for the page to start loading
|
|
73
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch { /* non-fatal — proceed with parsed slug */ }
|
|
77
|
-
return parsed;
|
|
78
|
-
}
|
|
79
|
-
// No slug given: read from current tab URL (original behaviour)
|
|
80
|
-
let urlStr;
|
|
81
|
-
try {
|
|
82
|
-
const { result } = await session.Runtime.evaluate({
|
|
83
|
-
expression: 'window.location.href',
|
|
84
|
-
returnByValue: true,
|
|
85
|
-
});
|
|
86
|
-
urlStr = result.value;
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
return {
|
|
90
|
-
error: "CONTEXT_ERROR",
|
|
91
|
-
message: "Failed to evaluate window.location.href on the active tab."
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
if (!urlStr) {
|
|
95
|
-
return {
|
|
96
|
-
error: "CONTEXT_ERROR",
|
|
97
|
-
message: "Active tab URL is empty."
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
let url;
|
|
101
|
-
try {
|
|
102
|
-
url = new URL(urlStr);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return {
|
|
106
|
-
error: "INVALID_URL",
|
|
107
|
-
message: "Active tab URL is invalid."
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (url.hostname !== "github.com") {
|
|
111
|
-
return {
|
|
112
|
-
error: "NOT_ON_GITHUB_REPO",
|
|
113
|
-
message: "Navigate to a GitHub repository first, or pass a 'repo' argument (e.g. \"owner/repo\")."
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const parts = url.pathname.split("/").filter(Boolean);
|
|
117
|
-
if (parts.length < 2) {
|
|
118
|
-
return {
|
|
119
|
-
error: "NOT_ON_GITHUB_REPO",
|
|
120
|
-
message: "Navigate to a specific GitHub repository first (e.g. github.com/owner/repo)."
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const owner = parts[0];
|
|
124
|
-
const repo = parts[1];
|
|
125
|
-
return { owner, repo };
|
|
126
|
-
}
|
|
127
|
-
export function isGithubError(result) {
|
|
128
|
-
return result && typeof result === "object" && "error" in result && "message" in result;
|
|
129
|
-
}
|
package/dist/tools/hover.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Weavetab — tools/hover.ts
|
|
3
|
-
* browser_hover: dispatch mouseMoveTo CDP event on a nodeId or selector.
|
|
4
|
-
* Weavetab by fy2ne
|
|
5
|
-
*/
|
|
6
|
-
import CDP from "chrome-remote-interface";
|
|
7
|
-
import type { Config } from "../config/loader.js";
|
|
8
|
-
export interface HoverOptions {
|
|
9
|
-
selector?: string;
|
|
10
|
-
nodeId?: number;
|
|
11
|
-
waitForChild?: string;
|
|
12
|
-
holdMs?: number;
|
|
13
|
-
}
|
|
14
|
-
export declare function browserHover(session: CDP.Client, options: HoverOptions, config: Config): Promise<{
|
|
15
|
-
success: boolean;
|
|
16
|
-
x: number;
|
|
17
|
-
y: number;
|
|
18
|
-
}>;
|
|
19
|
-
//# sourceMappingURL=hover.d.ts.map
|
package/dist/tools/hover.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export async function browserHover(session, options, config) {
|
|
2
|
-
const { selector, nodeId, waitForChild, holdMs = 300 } = options;
|
|
3
|
-
let x, y;
|
|
4
|
-
if (selector) {
|
|
5
|
-
const res = await session.Runtime.evaluate({
|
|
6
|
-
expression: `window.__Weavetab_getCoords(${JSON.stringify(selector)})`,
|
|
7
|
-
returnByValue: true
|
|
8
|
-
}).catch(() => null);
|
|
9
|
-
const coords = res?.result?.value;
|
|
10
|
-
if (!coords)
|
|
11
|
-
throw new Error(`Selector not found or unresolved: ${selector}`);
|
|
12
|
-
x = coords.x;
|
|
13
|
-
y = coords.y;
|
|
14
|
-
}
|
|
15
|
-
else if (nodeId) {
|
|
16
|
-
try {
|
|
17
|
-
const { model } = await session.DOM.getBoxModel({ backendNodeId: nodeId });
|
|
18
|
-
x = (model.content[0] + model.content[2]) / 2;
|
|
19
|
-
y = (model.content[1] + model.content[5]) / 2;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
throw new Error(`Failed to get box model for nodeId ${nodeId}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
throw new Error("browser_hover requires either selector or nodeId");
|
|
27
|
-
}
|
|
28
|
-
// Move mouse to element via CDP
|
|
29
|
-
await session.Input.dispatchMouseEvent({
|
|
30
|
-
type: "mouseMoved",
|
|
31
|
-
x,
|
|
32
|
-
y
|
|
33
|
-
});
|
|
34
|
-
// Dispatch both mouseenter AND mouseover (some sites only listen to one)
|
|
35
|
-
await session.Runtime.evaluate({
|
|
36
|
-
expression: `(function(sel) {
|
|
37
|
-
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
38
|
-
if (!el) return;
|
|
39
|
-
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false, cancelable: true }));
|
|
40
|
-
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
|
|
41
|
-
})(${JSON.stringify(selector || '')})`,
|
|
42
|
-
returnByValue: true
|
|
43
|
-
}).catch(() => { });
|
|
44
|
-
await new Promise(r => setTimeout(r, holdMs));
|
|
45
|
-
// If waiting for submenu, use browserWait
|
|
46
|
-
if (waitForChild) {
|
|
47
|
-
const { browserWait } = await import("./wait.js");
|
|
48
|
-
await browserWait(session, {
|
|
49
|
-
for: "element",
|
|
50
|
-
value: waitForChild,
|
|
51
|
-
timeout: 5000
|
|
52
|
-
}, config);
|
|
53
|
-
await new Promise(r => setTimeout(r, 200)); // let animation finish
|
|
54
|
-
}
|
|
55
|
-
return { success: true, x, y };
|
|
56
|
-
}
|
|
57
|
-
//# sourceMappingURL=hover.js.map
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* integration.test.ts — Phase 1.1
|
|
3
|
-
* Integration tests for click, type, and browser_map against real pages.
|
|
4
|
-
* Run: npx tsx src/tools/integration.test.ts
|
|
5
|
-
*/
|
|
6
|
-
import { loadConfigSync } from "../config/loader.js";
|
|
7
|
-
import { getOrConnect } from "../cdp/connector.js";
|
|
8
|
-
import { weaveClick } from "./click.js";
|
|
9
|
-
import { weaveType } from "./type.js";
|
|
10
|
-
import { browserMap } from "./read.js";
|
|
11
|
-
import { buildActionMap } from "../cdp/walker.js";
|
|
12
|
-
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
13
|
-
let passed = 0, failed = 0;
|
|
14
|
-
const assert = (cond, msg) => {
|
|
15
|
-
if (cond) {
|
|
16
|
-
console.error(` ✅ ${msg}`);
|
|
17
|
-
passed++;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
console.error(` ❌ ${msg}`);
|
|
21
|
-
failed++;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
async function main() {
|
|
25
|
-
const config = loadConfigSync();
|
|
26
|
-
const client = await getOrConnect(config);
|
|
27
|
-
const session = client.session;
|
|
28
|
-
// ── Test 1: browser_map returns ref IDs ────────────
|
|
29
|
-
console.error("\n── Test 1: browser_map returns ref IDs ──");
|
|
30
|
-
await session.Page.navigate({ url: "https://httpbin.org/forms/post" });
|
|
31
|
-
await sleep(3000);
|
|
32
|
-
const mapStr = await browserMap(session, { prune: true }, "default", config);
|
|
33
|
-
assert(typeof mapStr === "string" && mapStr.length > 80, "map returned markdown content");
|
|
34
|
-
const rawMap = await buildActionMap(session, { prune: true }, "default");
|
|
35
|
-
assert(rawMap.elements.length >= 3, "map has >= 3 elements");
|
|
36
|
-
const allValid = rawMap.elements.filter(Boolean).every(el => el.id && String(el.id).startsWith("w:"));
|
|
37
|
-
assert(allValid, "all elements have w: ref IDs");
|
|
38
|
-
// ── Test 2: weaveClick by label ────────────────────
|
|
39
|
-
console.error("\n── Test 2: weaveClick by label ──");
|
|
40
|
-
await session.Page.navigate({ url: "https://httpbin.org/forms/post" });
|
|
41
|
-
await sleep(3000);
|
|
42
|
-
const clickR = await weaveClick(session, { label: "Submit order" }, config);
|
|
43
|
-
assert(clickR.success === true, "click returned success");
|
|
44
|
-
assert(typeof clickR.clicked === "string" && clickR.clicked.length > 0, "clicked field set");
|
|
45
|
-
assert(typeof clickR.ref === "string" && clickR.ref.startsWith("w:"), "ref is w:NN format");
|
|
46
|
-
assert(typeof clickR.execution_ms === "number" && clickR.execution_ms > 0, "execution_ms > 0");
|
|
47
|
-
// ── Test 3: weaveType by ref ID ────────────────────
|
|
48
|
-
console.error("\n── Test 3: weaveType by ref ID ──");
|
|
49
|
-
await session.Page.navigate({ url: "https://httpbin.org/forms/post" });
|
|
50
|
-
await sleep(3000);
|
|
51
|
-
const elems = await buildActionMap(session, { prune: true }, "default");
|
|
52
|
-
const textbox = elems.elements.find(el => el.role === "textbox" || (el.role === "input" && !el.type));
|
|
53
|
-
if (textbox?.id) {
|
|
54
|
-
const typeR = await weaveType(session, { id: textbox.id, text: "Hello" }, config);
|
|
55
|
-
assert(typeR.success === true, "type returned success");
|
|
56
|
-
assert(typeof typeR.length === "number" && typeR.length > 0, "length > 0");
|
|
57
|
-
assert(typeof typeR.typed_into === "string", "typed_into is string");
|
|
58
|
-
assert(typeof typeR.execution_ms === "number" && typeR.execution_ms > 0, "execution_ms > 0");
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
assert(true, "no text input found (skip)");
|
|
62
|
-
}
|
|
63
|
-
// ── Test 4: weaveClick by ref ID ───────────────────
|
|
64
|
-
console.error("\n── Test 4: weaveClick by ref ID ──");
|
|
65
|
-
await session.Page.navigate({ url: "https://httpbin.org/forms/post" });
|
|
66
|
-
await sleep(3000);
|
|
67
|
-
const map2 = await buildActionMap(session, { prune: true }, "default");
|
|
68
|
-
const btn = map2.elements.find(r => r.role === "button");
|
|
69
|
-
if (btn?.id) {
|
|
70
|
-
const refR = await weaveClick(session, { id: btn.id }, config);
|
|
71
|
-
assert(refR.success === true, "ref-ID click returned success");
|
|
72
|
-
assert(refR.ref === btn.id, "click.ref matches input ref");
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
assert(true, "no button found (skip)");
|
|
76
|
-
}
|
|
77
|
-
// ── Test 5: Stuck detection on nonexistent element ─
|
|
78
|
-
console.error("\n── Test 5: Stuck detection ──");
|
|
79
|
-
const stuckR = await weaveClick(session, { label: "__nonexistent__" }, config);
|
|
80
|
-
if (stuckR.success) {
|
|
81
|
-
assert(stuckR.stuck_warning === true, "nonexistent click reports stuck_warning");
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
assert(true, "nonexistent click correctly fails");
|
|
85
|
-
}
|
|
86
|
-
// ── Summary ────────────────────────────────────────
|
|
87
|
-
console.error(`\nResults: ${passed} passed, ${failed} failed`);
|
|
88
|
-
await session.close().catch(() => { });
|
|
89
|
-
if (failed > 0)
|
|
90
|
-
process.exit(1);
|
|
91
|
-
else
|
|
92
|
-
console.error("ALL INTEGRATION TESTS PASSED");
|
|
93
|
-
process.exit(0);
|
|
94
|
-
}
|
|
95
|
-
main();
|
|
96
|
-
//# sourceMappingURL=integration.test.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { TrailEntry as TrailStep } from "../intelligence/trail.js";
|
|
2
|
-
import type { AnyBurstStep } from "./burst.js";
|
|
3
|
-
/**
|
|
4
|
-
* Compiles a sequence of successful trail steps into a single burst macro.
|
|
5
|
-
* This skips intermediate DOM reads for faster execution.
|
|
6
|
-
*/
|
|
7
|
-
export declare function compileTraceToMacro(steps: TrailStep[]): AnyBurstStep[];
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { logDebug } from "../audit/logger.js";
|
|
2
|
-
/**
|
|
3
|
-
* Universal parameter extractor — tries ALL known parameter key patterns plus
|
|
4
|
-
* runtime introspection to find the args container regardless of the source
|
|
5
|
-
* code path that produced the trail entry.
|
|
6
|
-
*/
|
|
7
|
-
function extractArgs(step) {
|
|
8
|
-
const s = step;
|
|
9
|
-
// 1. Known parameter key patterns (ordered by specificity)
|
|
10
|
-
const knownKeys = [
|
|
11
|
-
'arguments', 'args', 'mcp_arguments', 'input',
|
|
12
|
-
'params', 'parameters', 'tool_args', 'toolInput',
|
|
13
|
-
'data', 'payload', 'options',
|
|
14
|
-
];
|
|
15
|
-
for (const key of knownKeys) {
|
|
16
|
-
const val = s[key];
|
|
17
|
-
if (val !== null && val !== undefined && typeof val === 'object' && !Array.isArray(val)) {
|
|
18
|
-
return val;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
// 2. Runtime introspection: find the first own property that looks like
|
|
22
|
-
// a params container (non-null object, not a standard metadata key).
|
|
23
|
-
const metaKeys = new Set([
|
|
24
|
-
'tool', 'result', 'timestamp', 'type', 'id',
|
|
25
|
-
'status', 'error', 'duration', 'startTime', 'endTime',
|
|
26
|
-
'session', 'tabId', 'targetId',
|
|
27
|
-
]);
|
|
28
|
-
for (const key of Object.keys(s)) {
|
|
29
|
-
if (metaKeys.has(key))
|
|
30
|
-
continue;
|
|
31
|
-
const val = s[key];
|
|
32
|
-
if (val !== null && val !== undefined && typeof val === 'object' && !Array.isArray(val)) {
|
|
33
|
-
logDebug(`[macro_compiler] Found unknown param key "${key}" on step ${step.tool}`);
|
|
34
|
-
return val;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// 3. Totally empty — log the shape for debugging
|
|
38
|
-
logDebug(`[macro_compiler] Could not extract args from step ${step.tool}, shape: ${Object.keys(s).join(',')}`);
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Compiles a sequence of successful trail steps into a single burst macro.
|
|
43
|
-
* This skips intermediate DOM reads for faster execution.
|
|
44
|
-
*/
|
|
45
|
-
export function compileTraceToMacro(steps) {
|
|
46
|
-
const macro = [];
|
|
47
|
-
let lastKey = "";
|
|
48
|
-
const BURST_TOOLS = new Set([
|
|
49
|
-
"browser_click", "browser_type", "browser_navigate", "browser_go",
|
|
50
|
-
"browser_key", "browser_scroll", "browser_wait", "browser_select",
|
|
51
|
-
"browser_pointer", "browser_eval", "browser_console", "browser_find",
|
|
52
|
-
"browser_fill", "browser_type_secret", "browser_inspect",
|
|
53
|
-
"browser_screenshot", "browser_storage", "browser_cookies",
|
|
54
|
-
"browser_highlight", "browser_scrape",
|
|
55
|
-
"browser_canvas", "browser_performance", "browser_snapshot",
|
|
56
|
-
"browser_pdf", "browser_dialog", "browser_upload", "browser_automation",
|
|
57
|
-
"browser_viewport", "browser_clipboard", "browser_detect",
|
|
58
|
-
"browser_macro_compile", "browser_reset_loop_counter", "browser_recording",
|
|
59
|
-
"browser_fill", "type_and_send", "navigate_and_read",
|
|
60
|
-
"click_and_wait", "scroll_and_read",
|
|
61
|
-
]);
|
|
62
|
-
steps.forEach((step, idx) => {
|
|
63
|
-
if (step.result && !step.result.success)
|
|
64
|
-
return;
|
|
65
|
-
if (step.tool === "browser_map") {
|
|
66
|
-
if (idx === steps.length - 1) {
|
|
67
|
-
macro.push({ tool: "browser_map", args: step.input || {} });
|
|
68
|
-
}
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (BURST_TOOLS.has(step.tool)) {
|
|
72
|
-
const args = extractArgs(step);
|
|
73
|
-
// Dedupe consecutive identical steps (same tool + same args)
|
|
74
|
-
const key = `${step.tool}:${JSON.stringify(args)}`;
|
|
75
|
-
if (key !== lastKey) {
|
|
76
|
-
macro.push({ tool: step.tool, args });
|
|
77
|
-
lastKey = key;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
return macro;
|
|
82
|
-
}
|
package/dist/tools/mouse.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import CDP from "chrome-remote-interface";
|
|
2
|
-
import type { Config } from "../config/loader.js";
|
|
3
|
-
export interface MouseResult {
|
|
4
|
-
success: true;
|
|
5
|
-
action: string;
|
|
6
|
-
x: number;
|
|
7
|
-
y: number;
|
|
8
|
-
end_x?: number;
|
|
9
|
-
end_y?: number;
|
|
10
|
-
execution_ms: number;
|
|
11
|
-
tab_id: string;
|
|
12
|
-
url: string;
|
|
13
|
-
}
|
|
14
|
-
export declare function browserMouse(session: CDP.Client, args: {
|
|
15
|
-
action: "hover" | "click" | "double_click" | "drag_and_drop";
|
|
16
|
-
x: number;
|
|
17
|
-
y: number;
|
|
18
|
-
end_x?: number;
|
|
19
|
-
end_y?: number;
|
|
20
|
-
}, config: Config): Promise<MouseResult>;
|
|
21
|
-
//# sourceMappingURL=mouse.d.ts.map
|
package/dist/tools/mouse.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { logAction } from "../audit/logger.js";
|
|
2
|
-
import { recordAction } from "../intelligence/trail.js";
|
|
3
|
-
import { ghostMove } from "../overlay/ghost.js";
|
|
4
|
-
import { waitForDOMStable } from "../sensors/guard.js";
|
|
5
|
-
export async function browserMouse(session, args, config) {
|
|
6
|
-
const { action, x, y, end_x, end_y } = args;
|
|
7
|
-
const startMs = Date.now();
|
|
8
|
-
const { Page, Input } = session;
|
|
9
|
-
const { frameTree } = await Page.getFrameTree();
|
|
10
|
-
const url = frameTree.frame.url;
|
|
11
|
-
let targetId = "default";
|
|
12
|
-
const { sessionRegistry } = await import("../state/session.js");
|
|
13
|
-
const active = sessionRegistry.getActive();
|
|
14
|
-
if (active)
|
|
15
|
-
targetId = active.targetId;
|
|
16
|
-
// Move to target using human-like motion (no label — avoids fake "scanning" delay)
|
|
17
|
-
await ghostMove(session, x, y, config);
|
|
18
|
-
if (action === "click") {
|
|
19
|
-
await Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
20
|
-
await Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
21
|
-
}
|
|
22
|
-
else if (action === "double_click") {
|
|
23
|
-
await Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
24
|
-
await Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
25
|
-
await new Promise(r => setTimeout(r, 100));
|
|
26
|
-
await Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 2 });
|
|
27
|
-
await Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 2 });
|
|
28
|
-
}
|
|
29
|
-
else if (action === "drag_and_drop") {
|
|
30
|
-
if (end_x === undefined || end_y === undefined) {
|
|
31
|
-
throw new Error("drag_and_drop requires end_x and end_y");
|
|
32
|
-
}
|
|
33
|
-
await Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
34
|
-
await new Promise(r => setTimeout(r, 200));
|
|
35
|
-
// Drag to destination
|
|
36
|
-
await ghostMove(session, end_x, end_y, config);
|
|
37
|
-
await new Promise(r => setTimeout(r, 200));
|
|
38
|
-
await Input.dispatchMouseEvent({ type: "mouseReleased", x: end_x, y: end_y, button: "left", clickCount: 1 });
|
|
39
|
-
}
|
|
40
|
-
// Settle
|
|
41
|
-
await waitForDOMStable(session, 50, 1500).catch(() => { });
|
|
42
|
-
const executionMs = Date.now() - startMs;
|
|
43
|
-
logAction("browser_mouse", action, `(${x}, ${y}) (exec: ${executionMs}ms)`);
|
|
44
|
-
recordAction({
|
|
45
|
-
timestamp: new Date().toISOString(),
|
|
46
|
-
tool: "browser_mouse",
|
|
47
|
-
input: args,
|
|
48
|
-
result: { success: true },
|
|
49
|
-
});
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
action,
|
|
53
|
-
x,
|
|
54
|
-
y,
|
|
55
|
-
end_x,
|
|
56
|
-
end_y,
|
|
57
|
-
execution_ms: executionMs,
|
|
58
|
-
tab_id: targetId,
|
|
59
|
-
url,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=mouse.js.map
|