hypha-debugger 0.1.9 → 0.1.11
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/dist/hypha-debugger.js +213 -91
- package/dist/hypha-debugger.js.map +1 -1
- package/dist/hypha-debugger.min.js +7 -7
- package/dist/hypha-debugger.min.js.map +1 -1
- package/dist/hypha-debugger.mjs +213 -91
- package/dist/hypha-debugger.mjs.map +1 -1
- package/dist/services/screenshot.d.ts +5 -4
- package/package.json +1 -1
package/dist/hypha-debugger.js
CHANGED
|
@@ -10425,27 +10425,95 @@
|
|
|
10425
10425
|
|
|
10426
10426
|
/**
|
|
10427
10427
|
* Screenshot capture service using html-to-image.
|
|
10428
|
+
*
|
|
10429
|
+
* Images are downscaled before being returned so agents don't receive
|
|
10430
|
+
* multi-megabyte base64 payloads that can crash their context window.
|
|
10428
10431
|
*/
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10432
|
+
/**
|
|
10433
|
+
* Resize an image data URL via a canvas. Returns a new data URL at the
|
|
10434
|
+
* requested format/quality. Maintains aspect ratio: fits within
|
|
10435
|
+
* (maxWidth × maxHeight) without distortion.
|
|
10436
|
+
*/
|
|
10437
|
+
async function resizeDataUrl(dataUrl, maxWidth, maxHeight, format, quality) {
|
|
10438
|
+
return new Promise((resolve, reject) => {
|
|
10439
|
+
const img = new Image();
|
|
10440
|
+
img.onload = () => {
|
|
10441
|
+
const srcW = img.naturalWidth;
|
|
10442
|
+
const srcH = img.naturalHeight;
|
|
10443
|
+
// Compute scale to fit within bounds (but never upscale)
|
|
10444
|
+
const scale = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
|
|
10445
|
+
const dstW = Math.max(1, Math.round(srcW * scale));
|
|
10446
|
+
const dstH = Math.max(1, Math.round(srcH * scale));
|
|
10447
|
+
const canvas = document.createElement("canvas");
|
|
10448
|
+
canvas.width = dstW;
|
|
10449
|
+
canvas.height = dstH;
|
|
10450
|
+
const ctx = canvas.getContext("2d");
|
|
10451
|
+
if (!ctx) {
|
|
10452
|
+
reject(new Error("Could not get 2D canvas context"));
|
|
10453
|
+
return;
|
|
10454
|
+
}
|
|
10455
|
+
// Fill white background for JPEG (no alpha support)
|
|
10456
|
+
if (format === "jpeg") {
|
|
10457
|
+
ctx.fillStyle = "#ffffff";
|
|
10458
|
+
ctx.fillRect(0, 0, dstW, dstH);
|
|
10459
|
+
}
|
|
10460
|
+
ctx.drawImage(img, 0, 0, dstW, dstH);
|
|
10461
|
+
const mime = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
10462
|
+
const out = canvas.toDataURL(mime, quality);
|
|
10463
|
+
resolve({ dataUrl: out, width: dstW, height: dstH });
|
|
10464
|
+
};
|
|
10465
|
+
img.onerror = () => reject(new Error("Failed to load image for resizing"));
|
|
10466
|
+
img.src = dataUrl;
|
|
10467
|
+
});
|
|
10468
|
+
}
|
|
10469
|
+
async function takeScreenshot(selector, format, quality, max_width, max_height, full_page) {
|
|
10470
|
+
// Agent-friendly defaults: JPEG, moderate quality, capped at 1024px,
|
|
10471
|
+
// viewport-only (not the entire scrollable page).
|
|
10472
|
+
const fmt = format ?? "jpeg";
|
|
10473
|
+
const qual = quality ?? 0.75;
|
|
10474
|
+
const maxW = max_width ?? 1024;
|
|
10475
|
+
const maxH = max_height ?? 1024;
|
|
10476
|
+
const capturePage = full_page ?? false;
|
|
10477
|
+
// Pick target:
|
|
10478
|
+
// - explicit selector → that element
|
|
10479
|
+
// - full_page=true → document.documentElement (the entire scrollable page)
|
|
10480
|
+
// - default → viewport-sized region (clipped to window size)
|
|
10481
|
+
let target;
|
|
10482
|
+
if (selector) {
|
|
10483
|
+
target = document.querySelector(selector);
|
|
10484
|
+
if (!target) {
|
|
10485
|
+
return { error: `No element found for selector: ${selector}` };
|
|
10486
|
+
}
|
|
10487
|
+
}
|
|
10488
|
+
else if (capturePage) {
|
|
10489
|
+
target = document.documentElement;
|
|
10490
|
+
}
|
|
10491
|
+
else {
|
|
10492
|
+
target = document.body;
|
|
10436
10493
|
}
|
|
10437
10494
|
try {
|
|
10438
10495
|
const node = target;
|
|
10496
|
+
// For viewport-only captures, limit html-to-image's output size
|
|
10497
|
+
// to the viewport dimensions.
|
|
10498
|
+
const viewportW = window.innerWidth;
|
|
10499
|
+
const viewportH = window.innerHeight;
|
|
10439
10500
|
const captureOptions = {
|
|
10440
10501
|
quality: qual,
|
|
10441
|
-
pixelRatio:
|
|
10502
|
+
pixelRatio: 1, // always capture at 1x — we'll resize after
|
|
10442
10503
|
cacheBust: true,
|
|
10443
10504
|
skipAutoScale: true,
|
|
10444
|
-
// Filter out the debugger overlay itself
|
|
10445
10505
|
filter: (el) => {
|
|
10446
|
-
|
|
10506
|
+
// Exclude the debugger overlay and cursor from screenshots
|
|
10507
|
+
return (el.id !== "hypha-debugger-host" &&
|
|
10508
|
+
el.id !== "hypha-debugger-cursor" &&
|
|
10509
|
+
el.id !== "playwright-highlight-container");
|
|
10447
10510
|
},
|
|
10448
10511
|
};
|
|
10512
|
+
if (!selector && !capturePage) {
|
|
10513
|
+
// Viewport-only capture: constrain canvas to window size
|
|
10514
|
+
captureOptions.width = viewportW;
|
|
10515
|
+
captureOptions.height = viewportH;
|
|
10516
|
+
}
|
|
10449
10517
|
let dataUrl;
|
|
10450
10518
|
if (fmt === "jpeg") {
|
|
10451
10519
|
dataUrl = await toJpeg(node, captureOptions);
|
|
@@ -10453,26 +10521,15 @@
|
|
|
10453
10521
|
else {
|
|
10454
10522
|
dataUrl = await toPng(node, captureOptions);
|
|
10455
10523
|
}
|
|
10456
|
-
//
|
|
10457
|
-
const
|
|
10458
|
-
|
|
10459
|
-
let height = Math.round(rect.height * scl);
|
|
10460
|
-
// Optionally resize if too large
|
|
10461
|
-
if (max_width && width > max_width) {
|
|
10462
|
-
const ratio = max_width / width;
|
|
10463
|
-
width = max_width;
|
|
10464
|
-
height = Math.round(height * ratio);
|
|
10465
|
-
}
|
|
10466
|
-
if (max_height && height > max_height) {
|
|
10467
|
-
const ratio = max_height / height;
|
|
10468
|
-
height = max_height;
|
|
10469
|
-
width = Math.round(width * ratio);
|
|
10470
|
-
}
|
|
10524
|
+
// Resize down to fit within (maxW × maxH) and re-encode
|
|
10525
|
+
const resized = await resizeDataUrl(dataUrl, maxW, maxH, fmt, qual);
|
|
10526
|
+
const sizeKb = Math.round((resized.dataUrl.length * 0.75) / 1024); // rough base64 → bytes
|
|
10471
10527
|
return {
|
|
10472
|
-
data: dataUrl,
|
|
10528
|
+
data: resized.dataUrl,
|
|
10473
10529
|
format: fmt,
|
|
10474
|
-
width,
|
|
10475
|
-
height,
|
|
10530
|
+
width: resized.width,
|
|
10531
|
+
height: resized.height,
|
|
10532
|
+
size_kb: sizeKb,
|
|
10476
10533
|
};
|
|
10477
10534
|
}
|
|
10478
10535
|
catch (err) {
|
|
@@ -10481,34 +10538,37 @@
|
|
|
10481
10538
|
}
|
|
10482
10539
|
takeScreenshot.__schema__ = {
|
|
10483
10540
|
name: "takeScreenshot",
|
|
10484
|
-
description: "Capture a screenshot of the
|
|
10541
|
+
description: "Capture a screenshot of the current viewport, a specific element, or the full page. " +
|
|
10542
|
+
"Returns a base64-encoded data URL, downscaled to fit within max_width × max_height " +
|
|
10543
|
+
"(default 1024px) to keep the payload small enough for AI agents. Defaults to JPEG " +
|
|
10544
|
+
"format at 0.75 quality for reasonable file size.",
|
|
10485
10545
|
parameters: {
|
|
10486
10546
|
type: "object",
|
|
10487
10547
|
properties: {
|
|
10488
10548
|
selector: {
|
|
10489
10549
|
type: "string",
|
|
10490
|
-
description: "CSS selector of the element to capture. Omit to capture the
|
|
10550
|
+
description: "CSS selector of the element to capture. Omit to capture the viewport (or full page if full_page=true).",
|
|
10491
10551
|
},
|
|
10492
10552
|
format: {
|
|
10493
10553
|
type: "string",
|
|
10494
10554
|
enum: ["png", "jpeg"],
|
|
10495
|
-
description: 'Image format. Default: "png".',
|
|
10555
|
+
description: 'Image format. Default: "jpeg" (much smaller than PNG). Use "png" for sharp text.',
|
|
10496
10556
|
},
|
|
10497
10557
|
quality: {
|
|
10498
10558
|
type: "number",
|
|
10499
|
-
description: "
|
|
10500
|
-
},
|
|
10501
|
-
scale: {
|
|
10502
|
-
type: "number",
|
|
10503
|
-
description: "Pixel ratio / scale factor. Default: 1. Use 2 for retina.",
|
|
10559
|
+
description: "JPEG quality (0–1). Default: 0.75. Ignored for PNG. Lower = smaller payload.",
|
|
10504
10560
|
},
|
|
10505
10561
|
max_width: {
|
|
10506
10562
|
type: "number",
|
|
10507
|
-
description: "Maximum width in pixels. Image
|
|
10563
|
+
description: "Maximum output width in pixels. Default: 1024. Image is scaled down preserving aspect ratio.",
|
|
10508
10564
|
},
|
|
10509
10565
|
max_height: {
|
|
10510
10566
|
type: "number",
|
|
10511
|
-
description: "Maximum height in pixels. Image
|
|
10567
|
+
description: "Maximum output height in pixels. Default: 1024. Image is scaled down preserving aspect ratio.",
|
|
10568
|
+
},
|
|
10569
|
+
full_page: {
|
|
10570
|
+
type: "boolean",
|
|
10571
|
+
description: "If true, capture the entire scrollable page instead of just the viewport. Default: false.",
|
|
10512
10572
|
},
|
|
10513
10573
|
},
|
|
10514
10574
|
},
|
|
@@ -10517,12 +10577,56 @@
|
|
|
10517
10577
|
/**
|
|
10518
10578
|
* Arbitrary JavaScript execution service.
|
|
10519
10579
|
*/
|
|
10580
|
+
/**
|
|
10581
|
+
* Attempt to auto-return the last expression in a code block.
|
|
10582
|
+
* If the code doesn't contain an explicit `return`, we try to
|
|
10583
|
+
* add one to the last expression statement so the result is captured.
|
|
10584
|
+
*
|
|
10585
|
+
* Examples:
|
|
10586
|
+
* "document.title" → "return (document.title);"
|
|
10587
|
+
* "const x = 1; x + 2" → "const x = 1; return (x + 2);"
|
|
10588
|
+
* "const x = 1\nx + 2" → "const x = 1\nreturn (x + 2);"
|
|
10589
|
+
* "for(...) {}" → unchanged (control flow)
|
|
10590
|
+
* "return 42" → unchanged (explicit return)
|
|
10591
|
+
*/
|
|
10592
|
+
function autoReturn(code) {
|
|
10593
|
+
const trimmed = code.trim();
|
|
10594
|
+
// Already has a return statement? Leave it alone.
|
|
10595
|
+
if (/\breturn\b/.test(trimmed))
|
|
10596
|
+
return trimmed;
|
|
10597
|
+
// Split into statements: by newlines first, then by semicolons for
|
|
10598
|
+
// single-line multi-statement code like "const x = 1; x + 2"
|
|
10599
|
+
let lines = trimmed.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
10600
|
+
// If there's only one line with semicolons, split on semicolons
|
|
10601
|
+
if (lines.length === 1 && lines[0].includes(";")) {
|
|
10602
|
+
lines = lines[0].split(";").map((s) => s.trim()).filter(Boolean);
|
|
10603
|
+
}
|
|
10604
|
+
if (lines.length === 0)
|
|
10605
|
+
return trimmed;
|
|
10606
|
+
const lastLine = lines[lines.length - 1];
|
|
10607
|
+
// Don't add return to control flow, declarations, or assignment-only statements
|
|
10608
|
+
if (/^(if|for|while|switch|try|class|function |const |let |var |import |export )/.test(lastLine)) {
|
|
10609
|
+
return trimmed;
|
|
10610
|
+
}
|
|
10611
|
+
// Replace last statement with return
|
|
10612
|
+
lines[lines.length - 1] = "return (" + lastLine.replace(/;$/, "") + ");";
|
|
10613
|
+
return lines.join(";\n");
|
|
10614
|
+
}
|
|
10520
10615
|
async function executeScript(code, timeout_ms) {
|
|
10521
10616
|
const timeoutMs = timeout_ms ?? 10000;
|
|
10522
10617
|
try {
|
|
10618
|
+
// Try with auto-return first, fall back to original code if syntax error
|
|
10619
|
+
let execCode = autoReturn(code);
|
|
10620
|
+
let fn;
|
|
10621
|
+
try {
|
|
10622
|
+
fn = new Function("return (async () => {" + execCode + "})()");
|
|
10623
|
+
}
|
|
10624
|
+
catch {
|
|
10625
|
+
// Auto-return broke the syntax — use original code
|
|
10626
|
+
fn = new Function("return (async () => {" + code + "})()");
|
|
10627
|
+
}
|
|
10523
10628
|
const result = await Promise.race([
|
|
10524
|
-
|
|
10525
|
-
new Function("return (async () => {" + code + "})()")(),
|
|
10629
|
+
fn(),
|
|
10526
10630
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Execution timed out")), timeoutMs)),
|
|
10527
10631
|
]);
|
|
10528
10632
|
// Serialize the result safely
|
|
@@ -10537,6 +10641,7 @@
|
|
|
10537
10641
|
serialized = {
|
|
10538
10642
|
tag: result.tagName.toLowerCase(),
|
|
10539
10643
|
id: result.id,
|
|
10644
|
+
className: result.className,
|
|
10540
10645
|
text: (result.textContent ?? "").trim().slice(0, 500),
|
|
10541
10646
|
};
|
|
10542
10647
|
type = "HTMLElement";
|
|
@@ -10566,13 +10671,13 @@
|
|
|
10566
10671
|
}
|
|
10567
10672
|
executeScript.__schema__ = {
|
|
10568
10673
|
name: "executeScript",
|
|
10569
|
-
description:
|
|
10674
|
+
description: 'Execute arbitrary JavaScript code in the page context. Supports async/await. The last expression is auto-returned (no need for explicit "return"). Examples: "document.title", "document.querySelectorAll(\'a\').length", "await fetch(\'/api/data\').then(r => r.json())".',
|
|
10570
10675
|
parameters: {
|
|
10571
10676
|
type: "object",
|
|
10572
10677
|
properties: {
|
|
10573
10678
|
code: {
|
|
10574
10679
|
type: "string",
|
|
10575
|
-
description: 'JavaScript code to execute. The
|
|
10680
|
+
description: 'JavaScript code to execute. The last expression is automatically returned. Examples: "document.title", "document.querySelector(\'h1\').textContent".',
|
|
10576
10681
|
},
|
|
10577
10682
|
timeout_ms: {
|
|
10578
10683
|
type: "number",
|
|
@@ -11095,70 +11200,78 @@
|
|
|
11095
11200
|
"# Web Debugger Skill",
|
|
11096
11201
|
"",
|
|
11097
11202
|
"This skill allows you to remotely debug and interact with a web page through HTTP API endpoints.",
|
|
11203
|
+
"Pick the approach that fits your task — they can be combined freely.",
|
|
11204
|
+
"",
|
|
11205
|
+
"## Approaches",
|
|
11098
11206
|
"",
|
|
11099
|
-
"
|
|
11207
|
+
"### execute_script — Run Arbitrary JavaScript",
|
|
11100
11208
|
"",
|
|
11101
|
-
"The most
|
|
11209
|
+
"The most versatile function. Use it to read/modify page state, call APIs, query the DOM,",
|
|
11210
|
+
"or do anything JavaScript can do. The last expression is auto-returned (no need for `return`).",
|
|
11102
11211
|
"",
|
|
11103
|
-
"### Step 1: Observe the page",
|
|
11104
11212
|
"```bash",
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
"Elements are detected via smart heuristics: CSS cursor, ARIA roles, event listeners, tag names.",
|
|
11109
|
-
"Visual highlight labels are overlaid on the page for each detected element.",
|
|
11213
|
+
`# Read page state`,
|
|
11214
|
+
`curl -X POST '{SERVICE_URL}/execute_script' \\`,
|
|
11215
|
+
` -H 'Content-Type: application/json' -d '{"code": "document.title"}'`,
|
|
11110
11216
|
"",
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
"
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11217
|
+
`# Query DOM`,
|
|
11218
|
+
`curl -X POST '{SERVICE_URL}/execute_script' \\`,
|
|
11219
|
+
` -H 'Content-Type: application/json' -d '{"code": "document.querySelector(\\\"h1\\\").textContent"}'`,
|
|
11220
|
+
"",
|
|
11221
|
+
`# Call an API`,
|
|
11222
|
+
`curl -X POST '{SERVICE_URL}/execute_script' \\`,
|
|
11223
|
+
` -H 'Content-Type: application/json' -d '{"code": "await fetch(\\\"/api/data\\\").then(r => r.json())"}'`,
|
|
11224
|
+
"",
|
|
11225
|
+
`# Modify the page`,
|
|
11226
|
+
`curl -X POST '{SERVICE_URL}/execute_script' \\`,
|
|
11227
|
+
` -H 'Content-Type: application/json' -d '{"code": "document.getElementById(\\\"name\\\").value = \\\"Alice\\\""}'`,
|
|
11118
11228
|
"```",
|
|
11119
11229
|
"",
|
|
11120
|
-
"###
|
|
11230
|
+
"### get_browser_state + Index-Based Interaction",
|
|
11231
|
+
"",
|
|
11232
|
+
"Best for UI interaction as a user would — clicking buttons, filling forms, selecting options.",
|
|
11233
|
+
"All interactive elements are detected and indexed as `[0]`, `[1]`, `[2]`, etc.",
|
|
11234
|
+
"",
|
|
11121
11235
|
"```bash",
|
|
11122
|
-
|
|
11236
|
+
`# Step 1: See all interactive elements`,
|
|
11237
|
+
`curl '{SERVICE_URL}/get_browser_state'`,
|
|
11238
|
+
"",
|
|
11239
|
+
`# Step 2: Act by index`,
|
|
11123
11240
|
`curl -X POST '{SERVICE_URL}/click_element_by_index' \\`,
|
|
11124
11241
|
` -H 'Content-Type: application/json' -d '{"index": 2}'`,
|
|
11125
11242
|
"",
|
|
11126
|
-
"# Type into an input (e.g. [1] Search):",
|
|
11127
11243
|
`curl -X POST '{SERVICE_URL}/input_text' \\`,
|
|
11128
11244
|
` -H 'Content-Type: application/json' -d '{"index": 1, "text": "hello world"}'`,
|
|
11129
11245
|
"",
|
|
11130
|
-
"# Select a dropdown option (e.g. [3] Language):",
|
|
11131
11246
|
`curl -X POST '{SERVICE_URL}/select_option' \\`,
|
|
11132
11247
|
` -H 'Content-Type: application/json' -d '{"index": 3, "option_text": "French"}'`,
|
|
11133
11248
|
"",
|
|
11134
|
-
"# Scroll down:",
|
|
11135
11249
|
`curl -X POST '{SERVICE_URL}/scroll' \\`,
|
|
11136
11250
|
` -H 'Content-Type: application/json' -d '{"direction": "down"}'`,
|
|
11137
11251
|
"",
|
|
11138
|
-
|
|
11139
|
-
`curl -X POST '{SERVICE_URL}/scroll' \\`,
|
|
11140
|
-
` -H 'Content-Type: application/json' -d '{"direction": "down", "index": 4}'`,
|
|
11141
|
-
"```",
|
|
11142
|
-
"",
|
|
11143
|
-
"### Step 3: Verify",
|
|
11144
|
-
"```bash",
|
|
11252
|
+
`# Step 3: Verify visually`,
|
|
11145
11253
|
`curl '{SERVICE_URL}/take_screenshot'`,
|
|
11146
11254
|
"```",
|
|
11147
11255
|
"",
|
|
11148
|
-
"###
|
|
11256
|
+
"### get_react_tree — Inspect React Components",
|
|
11257
|
+
"",
|
|
11258
|
+
"If the page uses React, inspect component names, props, state, and hooks:",
|
|
11149
11259
|
"```bash",
|
|
11150
|
-
`curl '{SERVICE_URL}/
|
|
11260
|
+
`curl '{SERVICE_URL}/get_react_tree'`,
|
|
11151
11261
|
"```",
|
|
11152
11262
|
"",
|
|
11153
|
-
"
|
|
11263
|
+
"### CSS Selector-Based Functions",
|
|
11154
11264
|
"",
|
|
11155
|
-
"
|
|
11265
|
+
"Use CSS selectors directly when you know the element:",
|
|
11156
11266
|
"```bash",
|
|
11157
11267
|
`curl -X POST '{SERVICE_URL}/click_element' \\`,
|
|
11158
11268
|
` -H 'Content-Type: application/json' -d '{"selector": "button.submit"}'`,
|
|
11159
11269
|
"",
|
|
11160
11270
|
`curl -X POST '{SERVICE_URL}/fill_input' \\`,
|
|
11161
11271
|
` -H 'Content-Type: application/json' -d '{"selector": "#email", "value": "user@example.com"}'`,
|
|
11272
|
+
"",
|
|
11273
|
+
`curl -X POST '{SERVICE_URL}/query_dom' \\`,
|
|
11274
|
+
` -H 'Content-Type: application/json' -d '{"selector": ".product-card"}'`,
|
|
11162
11275
|
"```",
|
|
11163
11276
|
"",
|
|
11164
11277
|
"## How to call functions",
|
|
@@ -11234,14 +11347,14 @@
|
|
|
11234
11347
|
const tips = [
|
|
11235
11348
|
"## Tips",
|
|
11236
11349
|
"",
|
|
11237
|
-
"-
|
|
11238
|
-
"-
|
|
11350
|
+
"- **`execute_script` is the most versatile** — use it for reading state, calling APIs, DOM queries, or anything not covered by other functions. The last expression is auto-returned.",
|
|
11351
|
+
"- **`get_browser_state` is the best way to see what's on the page** — it detects all interactive elements and shows them as indexed items.",
|
|
11239
11352
|
"- **After each action, call `get_browser_state` again** — element indices change when the DOM updates.",
|
|
11240
11353
|
"- **Use `take_screenshot`** to visually verify the page state. Call `remove_highlights` first for a clean view.",
|
|
11241
|
-
"- **Use `execute_script`** for anything not covered by the built-in functions — it runs arbitrary JavaScript.",
|
|
11242
11354
|
"- **Use `scroll`** with an element index to scroll inside a specific container (e.g. a chat window, sidebar).",
|
|
11243
11355
|
"- **Use `get_page_info` with `include_logs=true`** to check for JavaScript errors or debug output.",
|
|
11244
|
-
"- **Use `get_react_tree`** if the page uses React — it gives you component names, props, and state.",
|
|
11356
|
+
"- **Use `get_react_tree`** if the page uses React — it gives you component names, props, and state without needing DevTools.",
|
|
11357
|
+
"- **Use `navigate`** to go to other pages — same-origin navigation auto-reconnects the debugger.",
|
|
11245
11358
|
"- All POST endpoints accept JSON body with the parameter names as keys.",
|
|
11246
11359
|
"",
|
|
11247
11360
|
].join("\n");
|
|
@@ -11275,14 +11388,24 @@
|
|
|
11275
11388
|
// Create a wrapper that:
|
|
11276
11389
|
// 1. Has correct, unminified parameter names (for hypha-rpc getParamNames)
|
|
11277
11390
|
// 2. Detects when kwargs are passed as a single object and destructures them
|
|
11391
|
+
//
|
|
11392
|
+
// hypha-rpc HTTP handler passes kwargs as a single plain object, e.g.:
|
|
11393
|
+
// execute_script({code: "..."}) instead of execute_script("...")
|
|
11394
|
+
// get_react_tree({}) instead of get_react_tree()
|
|
11395
|
+
// We detect this and destructure, or discard empty objects.
|
|
11278
11396
|
const paramList = paramNames.join(", ");
|
|
11397
|
+
const firstParam = paramNames[0];
|
|
11279
11398
|
const wrapper = new Function("fn", "paramNames", `return async function(${paramList}) {
|
|
11280
11399
|
// Detect kwargs-as-object: single argument that is a plain object
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
var
|
|
11284
|
-
|
|
11285
|
-
if (
|
|
11400
|
+
if (arguments.length === 1 && ${firstParam} != null && typeof ${firstParam} === "object" && !Array.isArray(${firstParam}) && !(${firstParam} instanceof Date) && ${firstParam}.constructor === Object) {
|
|
11401
|
+
var _kw = ${firstParam};
|
|
11402
|
+
var _keys = Object.keys(_kw);
|
|
11403
|
+
// Empty object {} → call with no args (all defaults)
|
|
11404
|
+
if (_keys.length === 0) {
|
|
11405
|
+
return fn();
|
|
11406
|
+
}
|
|
11407
|
+
// Keys match schema params → destructure
|
|
11408
|
+
if (paramNames.indexOf(_keys[0]) !== -1) {
|
|
11286
11409
|
var _args = paramNames.map(function(n) { return _kw[n]; });
|
|
11287
11410
|
return fn.apply(null, _args);
|
|
11288
11411
|
}
|
|
@@ -14372,22 +14495,21 @@
|
|
|
14372
14495
|
`# A debugger is attached to a live web page.`,
|
|
14373
14496
|
`# You can remotely inspect, interact with, and control this page via the HTTP API below.`,
|
|
14374
14497
|
`#`,
|
|
14375
|
-
`#
|
|
14376
|
-
`#
|
|
14377
|
-
`#
|
|
14378
|
-
`#
|
|
14379
|
-
|
|
14380
|
-
`#
|
|
14381
|
-
`# scroll_to, execute_script, navigate, get_react_tree, remove_highlights`,
|
|
14498
|
+
`# APPROACHES (pick what fits your task):`,
|
|
14499
|
+
`# - execute_script: Run arbitrary JS — fastest for reading/modifying page state, DOM queries, API calls`,
|
|
14500
|
+
`# - get_browser_state + click/input/select by index: Visual interaction with the page as a user would`,
|
|
14501
|
+
`# - get_html / query_dom: Read DOM structure with CSS selectors`,
|
|
14502
|
+
`# - get_react_tree: Inspect React component props, state, and hooks`,
|
|
14503
|
+
`# - take_screenshot: Visual verification of page state`,
|
|
14382
14504
|
`#`,
|
|
14383
|
-
`# POST endpoints accept JSON body with parameter names as keys.`,
|
|
14505
|
+
`# All POST endpoints accept JSON body with parameter names as keys.`,
|
|
14384
14506
|
``,
|
|
14385
14507
|
`SERVICE_URL="${serviceUrl}"`,
|
|
14386
14508
|
];
|
|
14387
14509
|
if (token) {
|
|
14388
14510
|
lines.push(`TOKEN="${token}"`);
|
|
14389
14511
|
}
|
|
14390
|
-
lines.push(``, `#
|
|
14512
|
+
lines.push(``, `# Execute JavaScript (most versatile — read state, call APIs, modify DOM):`, `curl -X POST "$SERVICE_URL/execute_script"${auth} -H "Content-Type: application/json" -d '{"code": "document.title"}'`, ``, `# Smart DOM analysis (indexed interactive elements for click/type/select):`, `curl "$SERVICE_URL/get_browser_state"${auth}`, ``, `# Interact by element index:`, `curl -X POST "$SERVICE_URL/click_element_by_index"${auth} -H "Content-Type: application/json" -d '{"index": 3}'`, `curl -X POST "$SERVICE_URL/input_text"${auth} -H "Content-Type: application/json" -d '{"index": 5, "text": "hello"}'`, ``, `# Screenshot + React inspection:`, `curl "$SERVICE_URL/take_screenshot"${auth}`, `curl "$SERVICE_URL/get_react_tree"${auth}`, ``, `# Navigate (auto-reconnects for same-origin):`, `curl -X POST "$SERVICE_URL/navigate"${auth} -H "Content-Type: application/json" -d '{"url": "/other-page"}'`, ``, `# Full API docs:`, `curl "$SERVICE_URL/get_skill_md"${auth}`);
|
|
14391
14513
|
return lines.join("\n");
|
|
14392
14514
|
}
|
|
14393
14515
|
/**
|