hypha-debugger 0.1.0 → 0.1.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/dist/debugger.d.ts +17 -0
- package/dist/hypha-debugger.js +8928 -358
- package/dist/hypha-debugger.js.map +1 -1
- package/dist/hypha-debugger.min.js +58 -1
- package/dist/hypha-debugger.min.js.map +1 -1
- package/dist/hypha-debugger.mjs +477 -341
- package/dist/hypha-debugger.mjs.map +1 -1
- package/dist/index.d.ts +8 -11
- package/dist/services/dom.d.ts +31 -4
- package/dist/services/execute.d.ts +1 -3
- package/dist/services/info.d.ts +16 -6
- package/dist/services/react.d.ts +1 -4
- package/dist/services/screenshot.d.ts +1 -8
- package/dist/services/skill.d.ts +29 -0
- package/dist/ui/overlay.d.ts +2 -2
- package/dist/ui/styles.d.ts +1 -1
- package/package.json +1 -1
package/dist/hypha-debugger.mjs
CHANGED
|
@@ -47,8 +47,8 @@ const overlayStyles = `
|
|
|
47
47
|
|
|
48
48
|
.debugger-panel {
|
|
49
49
|
display: none;
|
|
50
|
-
width:
|
|
51
|
-
max-height:
|
|
50
|
+
width: 380px;
|
|
51
|
+
max-height: 520px;
|
|
52
52
|
background: #1e1e2e;
|
|
53
53
|
color: #cdd6f4;
|
|
54
54
|
border-radius: 8px;
|
|
@@ -121,7 +121,7 @@ const overlayStyles = `
|
|
|
121
121
|
padding: 10px 12px;
|
|
122
122
|
overflow-y: auto;
|
|
123
123
|
flex: 1;
|
|
124
|
-
max-height:
|
|
124
|
+
max-height: 440px;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
.info-row {
|
|
@@ -145,12 +145,19 @@ const overlayStyles = `
|
|
|
145
145
|
font-size: 11px;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
.
|
|
148
|
+
.instructions-section {
|
|
149
149
|
margin-top: 8px;
|
|
150
150
|
padding-top: 6px;
|
|
151
151
|
border-top: 1px solid #313244;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
.instructions-header {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
margin-bottom: 4px;
|
|
159
|
+
}
|
|
160
|
+
|
|
154
161
|
.url-label {
|
|
155
162
|
font-size: 11px;
|
|
156
163
|
color: #6c7086;
|
|
@@ -159,25 +166,19 @@ const overlayStyles = `
|
|
|
159
166
|
margin-bottom: 3px;
|
|
160
167
|
}
|
|
161
168
|
|
|
162
|
-
.
|
|
163
|
-
display: flex;
|
|
164
|
-
align-items: center;
|
|
165
|
-
gap: 4px;
|
|
166
|
-
background: #313244;
|
|
167
|
-
border-radius: 3px;
|
|
168
|
-
padding: 4px 6px;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.url-text {
|
|
172
|
-
flex: 1;
|
|
169
|
+
.instructions-block {
|
|
173
170
|
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
174
171
|
font-size: 10px;
|
|
175
|
-
color: #
|
|
172
|
+
color: #a6e3a1;
|
|
173
|
+
background: #11111b;
|
|
174
|
+
border: 1px solid #313244;
|
|
175
|
+
border-radius: 4px;
|
|
176
|
+
padding: 8px;
|
|
177
|
+
white-space: pre-wrap;
|
|
176
178
|
word-break: break-all;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
-webkit-box-orient: vertical;
|
|
179
|
+
max-height: 200px;
|
|
180
|
+
overflow-y: auto;
|
|
181
|
+
line-height: 1.5;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
.copy-btn {
|
|
@@ -375,7 +376,11 @@ class DebugOverlay {
|
|
|
375
376
|
}
|
|
376
377
|
}
|
|
377
378
|
setInfo(info) {
|
|
378
|
-
|
|
379
|
+
// Remove only info rows, preserve instructions section
|
|
380
|
+
const oldRows = this.infoBody.querySelectorAll(".info-row");
|
|
381
|
+
oldRows.forEach((r) => r.remove());
|
|
382
|
+
// Insert info rows at the top of infoBody
|
|
383
|
+
const firstChild = this.infoBody.firstChild;
|
|
379
384
|
for (const [label, value] of Object.entries(info)) {
|
|
380
385
|
const row = document.createElement("div");
|
|
381
386
|
row.className = "info-row";
|
|
@@ -388,58 +393,38 @@ class DebugOverlay {
|
|
|
388
393
|
valueSpan.title = value;
|
|
389
394
|
row.appendChild(labelSpan);
|
|
390
395
|
row.appendChild(valueSpan);
|
|
391
|
-
this.infoBody.
|
|
396
|
+
this.infoBody.insertBefore(row, firstChild);
|
|
392
397
|
}
|
|
393
398
|
}
|
|
394
|
-
/** Show the
|
|
395
|
-
|
|
399
|
+
/** Show the instruction block with a copy-all button. */
|
|
400
|
+
setInstructions(instructions) {
|
|
401
|
+
// Remove any existing instruction section
|
|
402
|
+
const existing = this.infoBody.querySelector(".instructions-section");
|
|
403
|
+
if (existing)
|
|
404
|
+
existing.remove();
|
|
396
405
|
const section = document.createElement("div");
|
|
397
|
-
section.className = "
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
copyBtn.textContent = "Copy";
|
|
411
|
-
copyBtn.addEventListener("click", () => {
|
|
412
|
-
navigator.clipboard.writeText(url).then(() => {
|
|
413
|
-
copyBtn.textContent = "Copied!";
|
|
414
|
-
setTimeout(() => { copyBtn.textContent = "Copy"; }, 1500);
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
urlRow.appendChild(urlText);
|
|
418
|
-
urlRow.appendChild(copyBtn);
|
|
419
|
-
section.appendChild(urlRow);
|
|
420
|
-
const tokenLabel = document.createElement("div");
|
|
421
|
-
tokenLabel.className = "url-label";
|
|
422
|
-
tokenLabel.textContent = "Token";
|
|
423
|
-
tokenLabel.style.marginTop = "6px";
|
|
424
|
-
section.appendChild(tokenLabel);
|
|
425
|
-
const tokenRow = document.createElement("div");
|
|
426
|
-
tokenRow.className = "url-row";
|
|
427
|
-
const tokenText = document.createElement("span");
|
|
428
|
-
tokenText.className = "url-text";
|
|
429
|
-
tokenText.textContent = token.slice(0, 20) + "...";
|
|
430
|
-
tokenText.title = token;
|
|
431
|
-
const copyTokenBtn = document.createElement("button");
|
|
432
|
-
copyTokenBtn.className = "copy-btn";
|
|
433
|
-
copyTokenBtn.textContent = "Copy";
|
|
434
|
-
copyTokenBtn.addEventListener("click", () => {
|
|
435
|
-
navigator.clipboard.writeText(token).then(() => {
|
|
436
|
-
copyTokenBtn.textContent = "Copied!";
|
|
437
|
-
setTimeout(() => { copyTokenBtn.textContent = "Copy"; }, 1500);
|
|
406
|
+
section.className = "instructions-section";
|
|
407
|
+
const header = document.createElement("div");
|
|
408
|
+
header.className = "instructions-header";
|
|
409
|
+
const label = document.createElement("span");
|
|
410
|
+
label.className = "url-label";
|
|
411
|
+
label.textContent = "Instructions";
|
|
412
|
+
const copyAllBtn = document.createElement("button");
|
|
413
|
+
copyAllBtn.className = "copy-btn";
|
|
414
|
+
copyAllBtn.textContent = "Copy All";
|
|
415
|
+
copyAllBtn.addEventListener("click", () => {
|
|
416
|
+
navigator.clipboard.writeText(instructions).then(() => {
|
|
417
|
+
copyAllBtn.textContent = "Copied!";
|
|
418
|
+
setTimeout(() => { copyAllBtn.textContent = "Copy All"; }, 1500);
|
|
438
419
|
});
|
|
439
420
|
});
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
section.appendChild(
|
|
421
|
+
header.appendChild(label);
|
|
422
|
+
header.appendChild(copyAllBtn);
|
|
423
|
+
section.appendChild(header);
|
|
424
|
+
const pre = document.createElement("pre");
|
|
425
|
+
pre.className = "instructions-block";
|
|
426
|
+
pre.textContent = instructions;
|
|
427
|
+
section.appendChild(pre);
|
|
443
428
|
this.infoBody.appendChild(section);
|
|
444
429
|
}
|
|
445
430
|
addLog(message, type = "call") {
|
|
@@ -535,38 +520,34 @@ function collectPageInfo() {
|
|
|
535
520
|
/**
|
|
536
521
|
* Page information service.
|
|
537
522
|
*/
|
|
538
|
-
function getPageInfo() {
|
|
539
|
-
|
|
523
|
+
function getPageInfo(include_logs, log_limit, log_level) {
|
|
524
|
+
const info = collectPageInfo();
|
|
525
|
+
if (include_logs) {
|
|
526
|
+
const logs = window.__HYPHA_DEBUGGER__?.consoleLogs ?? [];
|
|
527
|
+
const limit = log_limit ?? 50;
|
|
528
|
+
let filtered = log_level ? logs.filter((l) => l.level === log_level) : logs;
|
|
529
|
+
info.console_logs = filtered.slice(-limit);
|
|
530
|
+
}
|
|
531
|
+
return info;
|
|
540
532
|
}
|
|
541
533
|
getPageInfo.__schema__ = {
|
|
542
534
|
name: "getPageInfo",
|
|
543
|
-
description: "Get information about the current web page including URL, title, viewport size, detected frameworks, and performance timing.",
|
|
544
|
-
parameters: {
|
|
545
|
-
type: "object",
|
|
546
|
-
properties: {},
|
|
547
|
-
},
|
|
548
|
-
};
|
|
549
|
-
function getConsoleLogs(options) {
|
|
550
|
-
const logs = window.__HYPHA_DEBUGGER__?.consoleLogs ?? [];
|
|
551
|
-
const level = options?.level;
|
|
552
|
-
const limit = options?.limit ?? 100;
|
|
553
|
-
let filtered = level ? logs.filter((l) => l.level === level) : logs;
|
|
554
|
-
return filtered.slice(-limit);
|
|
555
|
-
}
|
|
556
|
-
getConsoleLogs.__schema__ = {
|
|
557
|
-
name: "getConsoleLogs",
|
|
558
|
-
description: "Retrieve captured console output (log, warn, error, info).",
|
|
535
|
+
description: "Get information about the current web page including URL, title, viewport size, detected frameworks, and performance timing. Optionally include recent console logs.",
|
|
559
536
|
parameters: {
|
|
560
537
|
type: "object",
|
|
561
538
|
properties: {
|
|
562
|
-
|
|
563
|
-
type: "
|
|
564
|
-
description:
|
|
565
|
-
enum: ["log", "warn", "error", "info"],
|
|
539
|
+
include_logs: {
|
|
540
|
+
type: "boolean",
|
|
541
|
+
description: "If true, include recent console output in the response. Default: false.",
|
|
566
542
|
},
|
|
567
|
-
|
|
543
|
+
log_limit: {
|
|
568
544
|
type: "number",
|
|
569
|
-
description: "Maximum number of log entries to
|
|
545
|
+
description: "Maximum number of console log entries to include (most recent). Default: 50.",
|
|
546
|
+
},
|
|
547
|
+
log_level: {
|
|
548
|
+
type: "string",
|
|
549
|
+
description: 'Filter console logs by level: "log", "warn", "error", "info". Omit for all levels.',
|
|
550
|
+
enum: ["log", "warn", "error", "info"],
|
|
570
551
|
},
|
|
571
552
|
},
|
|
572
553
|
},
|
|
@@ -634,8 +615,8 @@ function elementToInfo(el) {
|
|
|
634
615
|
children_count: el.children.length,
|
|
635
616
|
};
|
|
636
617
|
}
|
|
637
|
-
function queryDom(selector,
|
|
638
|
-
|
|
618
|
+
function queryDom(selector, limit) {
|
|
619
|
+
limit = limit ?? 20;
|
|
639
620
|
const elements = document.querySelectorAll(selector);
|
|
640
621
|
const results = [];
|
|
641
622
|
for (let i = 0; i < Math.min(elements.length, limit); i++) {
|
|
@@ -770,86 +751,40 @@ scrollTo.__schema__ = {
|
|
|
770
751
|
required: ["target"],
|
|
771
752
|
},
|
|
772
753
|
};
|
|
773
|
-
function
|
|
774
|
-
const
|
|
754
|
+
function getHtml(selector, outer, max_length) {
|
|
755
|
+
const useOuter = outer ?? true;
|
|
756
|
+
const maxLen = max_length ?? 50000;
|
|
757
|
+
const el = selector ? document.querySelector(selector) : document.documentElement;
|
|
775
758
|
if (!el) {
|
|
776
759
|
return { error: `No element found for selector: ${selector}` };
|
|
777
760
|
}
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
const props = properties ??
|
|
781
|
-
[
|
|
782
|
-
"display",
|
|
783
|
-
"position",
|
|
784
|
-
"width",
|
|
785
|
-
"height",
|
|
786
|
-
"color",
|
|
787
|
-
"background-color",
|
|
788
|
-
"font-size",
|
|
789
|
-
"font-family",
|
|
790
|
-
"margin",
|
|
791
|
-
"padding",
|
|
792
|
-
"border",
|
|
793
|
-
"opacity",
|
|
794
|
-
"visibility",
|
|
795
|
-
"overflow",
|
|
796
|
-
"z-index",
|
|
797
|
-
];
|
|
798
|
-
for (const prop of props) {
|
|
799
|
-
result[prop] = computed.getPropertyValue(prop);
|
|
800
|
-
}
|
|
801
|
-
return result;
|
|
802
|
-
}
|
|
803
|
-
getComputedStyles.__schema__ = {
|
|
804
|
-
name: "getComputedStyles",
|
|
805
|
-
description: "Get computed CSS styles for an element.",
|
|
806
|
-
parameters: {
|
|
807
|
-
type: "object",
|
|
808
|
-
properties: {
|
|
809
|
-
selector: {
|
|
810
|
-
type: "string",
|
|
811
|
-
description: "CSS selector of the element.",
|
|
812
|
-
},
|
|
813
|
-
properties: {
|
|
814
|
-
type: "array",
|
|
815
|
-
items: { type: "string" },
|
|
816
|
-
description: 'CSS property names to retrieve, e.g. ["color", "font-size"]. Omit for common defaults.',
|
|
817
|
-
},
|
|
818
|
-
},
|
|
819
|
-
required: ["selector"],
|
|
820
|
-
},
|
|
821
|
-
};
|
|
822
|
-
function getElementBounds(selector) {
|
|
823
|
-
const el = document.querySelector(selector);
|
|
824
|
-
if (!el) {
|
|
825
|
-
return { error: `No element found for selector: ${selector}` };
|
|
826
|
-
}
|
|
827
|
-
const rect = el.getBoundingClientRect();
|
|
761
|
+
const raw = useOuter ? el.outerHTML : el.innerHTML;
|
|
762
|
+
const truncated = raw.length > maxLen;
|
|
828
763
|
return {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
width: Math.round(rect.width),
|
|
833
|
-
height: Math.round(rect.height),
|
|
834
|
-
},
|
|
835
|
-
visible: rect.width > 0 &&
|
|
836
|
-
rect.height > 0 &&
|
|
837
|
-
getComputedStyle(el).visibility !== "hidden" &&
|
|
838
|
-
getComputedStyle(el).display !== "none",
|
|
764
|
+
html: truncated ? raw.slice(0, maxLen) : raw,
|
|
765
|
+
length: raw.length,
|
|
766
|
+
truncated,
|
|
839
767
|
};
|
|
840
768
|
}
|
|
841
|
-
|
|
842
|
-
name: "
|
|
843
|
-
description: "Get the
|
|
769
|
+
getHtml.__schema__ = {
|
|
770
|
+
name: "getHtml",
|
|
771
|
+
description: "Get the HTML content of the page or a specific element. Returns outerHTML by default. Useful for understanding page structure.",
|
|
844
772
|
parameters: {
|
|
845
773
|
type: "object",
|
|
846
774
|
properties: {
|
|
847
775
|
selector: {
|
|
848
776
|
type: "string",
|
|
849
|
-
description: "CSS selector of the element.",
|
|
777
|
+
description: "CSS selector of the element. Omit to get the full page HTML.",
|
|
778
|
+
},
|
|
779
|
+
outer: {
|
|
780
|
+
type: "boolean",
|
|
781
|
+
description: "If true (default), return outerHTML (includes the element itself). If false, return innerHTML (children only).",
|
|
782
|
+
},
|
|
783
|
+
max_length: {
|
|
784
|
+
type: "number",
|
|
785
|
+
description: "Maximum character length of the returned HTML. Default: 50000. Result will be truncated if longer.",
|
|
850
786
|
},
|
|
851
787
|
},
|
|
852
|
-
required: ["selector"],
|
|
853
788
|
},
|
|
854
789
|
};
|
|
855
790
|
|
|
@@ -1737,11 +1672,10 @@ async function toJpeg(node, options = {}) {
|
|
|
1737
1672
|
/**
|
|
1738
1673
|
* Screenshot capture service using html-to-image.
|
|
1739
1674
|
*/
|
|
1740
|
-
async function takeScreenshot(
|
|
1741
|
-
const
|
|
1742
|
-
const
|
|
1743
|
-
const
|
|
1744
|
-
const scale = options?.scale ?? 1;
|
|
1675
|
+
async function takeScreenshot(selector, format, quality, scale, max_width, max_height) {
|
|
1676
|
+
const fmt = format ?? "png";
|
|
1677
|
+
const qual = quality ?? 0.92;
|
|
1678
|
+
const scl = scale ?? 1;
|
|
1745
1679
|
const target = selector ? document.querySelector(selector) : document.body;
|
|
1746
1680
|
if (!target) {
|
|
1747
1681
|
return { error: `No element found for selector: ${selector}` };
|
|
@@ -1749,8 +1683,8 @@ async function takeScreenshot(options) {
|
|
|
1749
1683
|
try {
|
|
1750
1684
|
const node = target;
|
|
1751
1685
|
const captureOptions = {
|
|
1752
|
-
quality,
|
|
1753
|
-
pixelRatio:
|
|
1686
|
+
quality: qual,
|
|
1687
|
+
pixelRatio: scl,
|
|
1754
1688
|
cacheBust: true,
|
|
1755
1689
|
skipAutoScale: true,
|
|
1756
1690
|
// Filter out the debugger overlay itself
|
|
@@ -1759,7 +1693,7 @@ async function takeScreenshot(options) {
|
|
|
1759
1693
|
},
|
|
1760
1694
|
};
|
|
1761
1695
|
let dataUrl;
|
|
1762
|
-
if (
|
|
1696
|
+
if (fmt === "jpeg") {
|
|
1763
1697
|
dataUrl = await toJpeg(node, captureOptions);
|
|
1764
1698
|
}
|
|
1765
1699
|
else {
|
|
@@ -1767,24 +1701,22 @@ async function takeScreenshot(options) {
|
|
|
1767
1701
|
}
|
|
1768
1702
|
// Get dimensions
|
|
1769
1703
|
const rect = node.getBoundingClientRect();
|
|
1770
|
-
let width = Math.round(rect.width *
|
|
1771
|
-
let height = Math.round(rect.height *
|
|
1704
|
+
let width = Math.round(rect.width * scl);
|
|
1705
|
+
let height = Math.round(rect.height * scl);
|
|
1772
1706
|
// Optionally resize if too large
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const ratio = maxW / width;
|
|
1777
|
-
width = maxW;
|
|
1707
|
+
if (max_width && width > max_width) {
|
|
1708
|
+
const ratio = max_width / width;
|
|
1709
|
+
width = max_width;
|
|
1778
1710
|
height = Math.round(height * ratio);
|
|
1779
1711
|
}
|
|
1780
|
-
if (
|
|
1781
|
-
const ratio =
|
|
1782
|
-
height =
|
|
1712
|
+
if (max_height && height > max_height) {
|
|
1713
|
+
const ratio = max_height / height;
|
|
1714
|
+
height = max_height;
|
|
1783
1715
|
width = Math.round(width * ratio);
|
|
1784
1716
|
}
|
|
1785
1717
|
return {
|
|
1786
1718
|
data: dataUrl,
|
|
1787
|
-
format,
|
|
1719
|
+
format: fmt,
|
|
1788
1720
|
width,
|
|
1789
1721
|
height,
|
|
1790
1722
|
};
|
|
@@ -1831,8 +1763,8 @@ takeScreenshot.__schema__ = {
|
|
|
1831
1763
|
/**
|
|
1832
1764
|
* Arbitrary JavaScript execution service.
|
|
1833
1765
|
*/
|
|
1834
|
-
async function executeScript(code,
|
|
1835
|
-
const timeoutMs =
|
|
1766
|
+
async function executeScript(code, timeout_ms) {
|
|
1767
|
+
const timeoutMs = timeout_ms ?? 10000;
|
|
1836
1768
|
try {
|
|
1837
1769
|
const result = await Promise.race([
|
|
1838
1770
|
// Use async Function to allow top-level await in the code
|
|
@@ -1923,60 +1855,6 @@ navigate.__schema__ = {
|
|
|
1923
1855
|
required: ["url"],
|
|
1924
1856
|
},
|
|
1925
1857
|
};
|
|
1926
|
-
function goBack() {
|
|
1927
|
-
try {
|
|
1928
|
-
window.history.back();
|
|
1929
|
-
return { success: true, message: "Navigated back" };
|
|
1930
|
-
}
|
|
1931
|
-
catch (err) {
|
|
1932
|
-
return { success: false, message: `Back navigation failed: ${err.message ?? err}` };
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
goBack.__schema__ = {
|
|
1936
|
-
name: "goBack",
|
|
1937
|
-
description: "Navigate back in browser history.",
|
|
1938
|
-
parameters: {
|
|
1939
|
-
type: "object",
|
|
1940
|
-
properties: {},
|
|
1941
|
-
},
|
|
1942
|
-
};
|
|
1943
|
-
function goForward() {
|
|
1944
|
-
try {
|
|
1945
|
-
window.history.forward();
|
|
1946
|
-
return { success: true, message: "Navigated forward" };
|
|
1947
|
-
}
|
|
1948
|
-
catch (err) {
|
|
1949
|
-
return {
|
|
1950
|
-
success: false,
|
|
1951
|
-
message: `Forward navigation failed: ${err.message ?? err}`,
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
goForward.__schema__ = {
|
|
1956
|
-
name: "goForward",
|
|
1957
|
-
description: "Navigate forward in browser history.",
|
|
1958
|
-
parameters: {
|
|
1959
|
-
type: "object",
|
|
1960
|
-
properties: {},
|
|
1961
|
-
},
|
|
1962
|
-
};
|
|
1963
|
-
function reload() {
|
|
1964
|
-
try {
|
|
1965
|
-
window.location.reload();
|
|
1966
|
-
return { success: true, message: "Reloading page" };
|
|
1967
|
-
}
|
|
1968
|
-
catch (err) {
|
|
1969
|
-
return { success: false, message: `Reload failed: ${err.message ?? err}` };
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
reload.__schema__ = {
|
|
1973
|
-
name: "reload",
|
|
1974
|
-
description: "Reload the current page.",
|
|
1975
|
-
parameters: {
|
|
1976
|
-
type: "object",
|
|
1977
|
-
properties: {},
|
|
1978
|
-
},
|
|
1979
|
-
};
|
|
1980
1858
|
|
|
1981
1859
|
/**
|
|
1982
1860
|
* React component tree inspection service.
|
|
@@ -2096,9 +1974,9 @@ function fiberToInfo(fiber, depth, maxDepth) {
|
|
|
2096
1974
|
}
|
|
2097
1975
|
return info;
|
|
2098
1976
|
}
|
|
2099
|
-
function getReactTree(
|
|
2100
|
-
|
|
2101
|
-
const maxDepth =
|
|
1977
|
+
function getReactTree(selector, max_depth) {
|
|
1978
|
+
selector = selector ?? "#root";
|
|
1979
|
+
const maxDepth = max_depth ?? 5;
|
|
2102
1980
|
const rootEl = document.querySelector(selector);
|
|
2103
1981
|
if (!rootEl) {
|
|
2104
1982
|
return { error: `No element found for selector: ${selector}` };
|
|
@@ -2142,6 +2020,133 @@ getReactTree.__schema__ = {
|
|
|
2142
2020
|
},
|
|
2143
2021
|
};
|
|
2144
2022
|
|
|
2023
|
+
/**
|
|
2024
|
+
* Dynamic SKILL.md generation following the agentskills.io specification.
|
|
2025
|
+
* Generates documentation from registered service function schemas.
|
|
2026
|
+
*/
|
|
2027
|
+
/**
|
|
2028
|
+
* Generate a SKILL.md document from a service definition.
|
|
2029
|
+
* Excludes credentials (service URL, token) — those are provided separately.
|
|
2030
|
+
*/
|
|
2031
|
+
function generateSkillMd(serviceFunctions, serviceUrl) {
|
|
2032
|
+
const frontmatter = [
|
|
2033
|
+
"---",
|
|
2034
|
+
"name: web-debugger",
|
|
2035
|
+
"description: Remote web page debugger. Inspect DOM, take screenshots, execute JavaScript, fill forms, click elements, and navigate pages — all via HTTP API calls.",
|
|
2036
|
+
"compatibility: Requires network access to the Hypha server. Works with any HTTP client (curl, fetch, Python requests).",
|
|
2037
|
+
"metadata:",
|
|
2038
|
+
' version: "0.1"',
|
|
2039
|
+
' author: "hypha-debugger"',
|
|
2040
|
+
"---",
|
|
2041
|
+
].join("\n");
|
|
2042
|
+
const intro = [
|
|
2043
|
+
"",
|
|
2044
|
+
"# Web Debugger Skill",
|
|
2045
|
+
"",
|
|
2046
|
+
"This skill allows you to remotely debug and interact with a web page through a set of HTTP API endpoints.",
|
|
2047
|
+
"",
|
|
2048
|
+
"## How to call functions",
|
|
2049
|
+
"",
|
|
2050
|
+
"All functions are available as HTTP endpoints. Use the service URL provided in the instructions.",
|
|
2051
|
+
"",
|
|
2052
|
+
"**GET request** (for functions with no required parameters):",
|
|
2053
|
+
"```",
|
|
2054
|
+
`curl '{SERVICE_URL}/get_page_info?_mode=last' -H 'Authorization: Bearer {TOKEN}'`,
|
|
2055
|
+
"```",
|
|
2056
|
+
"",
|
|
2057
|
+
"**POST request** (for functions with parameters):",
|
|
2058
|
+
"```",
|
|
2059
|
+
`curl -X POST '{SERVICE_URL}/query_dom?_mode=last' \\`,
|
|
2060
|
+
` -H 'Authorization: Bearer {TOKEN}' \\`,
|
|
2061
|
+
` -H 'Content-Type: application/json' \\`,
|
|
2062
|
+
` -d '{"selector": "button"}'`,
|
|
2063
|
+
"```",
|
|
2064
|
+
"",
|
|
2065
|
+
"Replace `{SERVICE_URL}` and `{TOKEN}` with the actual values from the instruction block.",
|
|
2066
|
+
"",
|
|
2067
|
+
"**Note:** The `_mode=last` query parameter ensures the latest debugger instance is used,",
|
|
2068
|
+
"even if multiple sessions have connected to the same workspace.",
|
|
2069
|
+
"",
|
|
2070
|
+
].join("\n");
|
|
2071
|
+
// Build the function reference
|
|
2072
|
+
const functionDocs = ["## Available Functions", ""];
|
|
2073
|
+
const entries = Object.entries(serviceFunctions).filter(([name, fn]) => fn?.__schema__ && name !== "get_skill_md");
|
|
2074
|
+
for (const [name, fn] of entries) {
|
|
2075
|
+
const schema = fn.__schema__;
|
|
2076
|
+
functionDocs.push(`### \`${name}\``);
|
|
2077
|
+
functionDocs.push("");
|
|
2078
|
+
functionDocs.push(schema.description);
|
|
2079
|
+
functionDocs.push("");
|
|
2080
|
+
const props = schema.parameters?.properties;
|
|
2081
|
+
const required = schema.parameters?.required ?? [];
|
|
2082
|
+
if (props && Object.keys(props).length > 0) {
|
|
2083
|
+
functionDocs.push("**Parameters:**");
|
|
2084
|
+
functionDocs.push("");
|
|
2085
|
+
functionDocs.push("| Parameter | Type | Required | Description |");
|
|
2086
|
+
functionDocs.push("|-----------|------|----------|-------------|");
|
|
2087
|
+
for (const [param, info] of Object.entries(props)) {
|
|
2088
|
+
const isRequired = required.includes(param);
|
|
2089
|
+
let typeStr = info.type ?? "any";
|
|
2090
|
+
if (info.enum)
|
|
2091
|
+
typeStr = info.enum.map((e) => `"${e}"`).join(" | ");
|
|
2092
|
+
if (info.items)
|
|
2093
|
+
typeStr = `${info.items.type}[]`;
|
|
2094
|
+
functionDocs.push(`| \`${param}\` | ${typeStr} | ${isRequired ? "Yes" : "No"} | ${info.description ?? ""} |`);
|
|
2095
|
+
}
|
|
2096
|
+
functionDocs.push("");
|
|
2097
|
+
// Example curl
|
|
2098
|
+
if (required.length > 0) {
|
|
2099
|
+
const exampleParams = {};
|
|
2100
|
+
for (const p of required) {
|
|
2101
|
+
const info = props[p];
|
|
2102
|
+
if (info?.type === "string")
|
|
2103
|
+
exampleParams[p] = `<${p}>`;
|
|
2104
|
+
else if (info?.type === "number")
|
|
2105
|
+
exampleParams[p] = "0";
|
|
2106
|
+
else
|
|
2107
|
+
exampleParams[p] = `<${p}>`;
|
|
2108
|
+
}
|
|
2109
|
+
functionDocs.push("**Example:**");
|
|
2110
|
+
functionDocs.push("```bash");
|
|
2111
|
+
functionDocs.push(`curl -X POST '{SERVICE_URL}/${name}?_mode=last' \\`);
|
|
2112
|
+
functionDocs.push(` -H 'Authorization: Bearer {TOKEN}' \\`);
|
|
2113
|
+
functionDocs.push(` -H 'Content-Type: application/json' \\`);
|
|
2114
|
+
functionDocs.push(` -d '${JSON.stringify(exampleParams)}'`);
|
|
2115
|
+
functionDocs.push("```");
|
|
2116
|
+
}
|
|
2117
|
+
else {
|
|
2118
|
+
functionDocs.push("**Example:**");
|
|
2119
|
+
functionDocs.push("```bash");
|
|
2120
|
+
functionDocs.push(`curl '{SERVICE_URL}/${name}?_mode=last' -H 'Authorization: Bearer {TOKEN}'`);
|
|
2121
|
+
functionDocs.push("```");
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
functionDocs.push("**Parameters:** None");
|
|
2126
|
+
functionDocs.push("");
|
|
2127
|
+
functionDocs.push("**Example:**");
|
|
2128
|
+
functionDocs.push("```bash");
|
|
2129
|
+
functionDocs.push(`curl '{SERVICE_URL}/${name}?_mode=last' -H 'Authorization: Bearer {TOKEN}'`);
|
|
2130
|
+
functionDocs.push("```");
|
|
2131
|
+
}
|
|
2132
|
+
functionDocs.push("");
|
|
2133
|
+
}
|
|
2134
|
+
const tips = [
|
|
2135
|
+
"## Tips",
|
|
2136
|
+
"",
|
|
2137
|
+
"- **Start with `get_page_info`** to understand the page structure, URL, title, and viewport.",
|
|
2138
|
+
"- **Use `query_dom`** with CSS selectors to find elements before clicking or filling them.",
|
|
2139
|
+
"- **Use `take_screenshot`** to visually verify the page state.",
|
|
2140
|
+
"- **Use `execute_script`** for anything not covered by the built-in functions — it runs arbitrary JavaScript.",
|
|
2141
|
+
"- **Use `get_page_info` with `include_logs=true`** to check for JavaScript errors or debug output.",
|
|
2142
|
+
"- **Use `get_react_tree`** if the page uses React — it gives you component names, props, and state.",
|
|
2143
|
+
"- All POST endpoints accept JSON body with the parameter names as keys.",
|
|
2144
|
+
"- All endpoints require the `Authorization: Bearer {TOKEN}` header.",
|
|
2145
|
+
"",
|
|
2146
|
+
].join("\n");
|
|
2147
|
+
return [frontmatter, intro, functionDocs.join("\n"), tips].join("\n");
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2145
2150
|
/**
|
|
2146
2151
|
* Core debugger class: connects to Hypha and registers the debug service.
|
|
2147
2152
|
*/
|
|
@@ -2187,93 +2192,16 @@ class HyphaDebugger {
|
|
|
2187
2192
|
if (this.config.token)
|
|
2188
2193
|
connectConfig.token = this.config.token;
|
|
2189
2194
|
this.server = await connect(connectConfig);
|
|
2190
|
-
// Wrap service functions with logging
|
|
2191
|
-
const wrapFn = (fn, name) => {
|
|
2192
|
-
const wrapped = async (...args) => {
|
|
2193
|
-
this.overlay?.addLog(`${name}(${this.summarizeArgs(args)})`, "call");
|
|
2194
|
-
try {
|
|
2195
|
-
const result = await fn(...args);
|
|
2196
|
-
const hasError = result && typeof result === "object" && "error" in result;
|
|
2197
|
-
if (hasError) {
|
|
2198
|
-
this.overlay?.addLog(`${name}: ${result.error}`, "error");
|
|
2199
|
-
}
|
|
2200
|
-
else {
|
|
2201
|
-
this.overlay?.addLog(`${name} -> OK`, "result");
|
|
2202
|
-
}
|
|
2203
|
-
return result;
|
|
2204
|
-
}
|
|
2205
|
-
catch (err) {
|
|
2206
|
-
this.overlay?.addLog(`${name}: ${err.message}`, "error");
|
|
2207
|
-
throw err;
|
|
2208
|
-
}
|
|
2209
|
-
};
|
|
2210
|
-
// Preserve schema
|
|
2211
|
-
if (fn.__schema__) {
|
|
2212
|
-
wrapped.__schema__ = fn.__schema__;
|
|
2213
|
-
}
|
|
2214
|
-
return wrapped;
|
|
2215
|
-
};
|
|
2216
2195
|
// Register debug service
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
type: "debugger",
|
|
2221
|
-
description: "Remote web page debugger. Allows inspecting DOM, taking screenshots, executing JavaScript, and interacting with the page.",
|
|
2222
|
-
config: {
|
|
2223
|
-
visibility: this.config.visibility,
|
|
2224
|
-
},
|
|
2225
|
-
// Service functions
|
|
2226
|
-
get_page_info: wrapFn(getPageInfo, "get_page_info"),
|
|
2227
|
-
get_console_logs: wrapFn(getConsoleLogs, "get_console_logs"),
|
|
2228
|
-
query_dom: wrapFn(queryDom, "query_dom"),
|
|
2229
|
-
click_element: wrapFn(clickElement, "click_element"),
|
|
2230
|
-
fill_input: wrapFn(fillInput, "fill_input"),
|
|
2231
|
-
scroll_to: wrapFn(scrollTo, "scroll_to"),
|
|
2232
|
-
get_computed_styles: wrapFn(getComputedStyles, "get_computed_styles"),
|
|
2233
|
-
get_element_bounds: wrapFn(getElementBounds, "get_element_bounds"),
|
|
2234
|
-
take_screenshot: wrapFn(takeScreenshot, "take_screenshot"),
|
|
2235
|
-
execute_script: wrapFn(executeScript, "execute_script"),
|
|
2236
|
-
navigate: wrapFn(navigate, "navigate"),
|
|
2237
|
-
go_back: wrapFn(goBack, "go_back"),
|
|
2238
|
-
go_forward: wrapFn(goForward, "go_forward"),
|
|
2239
|
-
reload: wrapFn(reload, "reload"),
|
|
2240
|
-
get_react_tree: wrapFn(getReactTree, "get_react_tree"),
|
|
2241
|
-
};
|
|
2242
|
-
this.serviceInfo = await this.server.registerService(service);
|
|
2243
|
-
// Update UI
|
|
2244
|
-
const workspace = this.server.config.workspace;
|
|
2245
|
-
const fullServiceId = this.serviceInfo.id ?? this.config.service_id;
|
|
2246
|
-
// Generate a token for remote HTTP access
|
|
2247
|
-
const sessionToken = await this.server.generateToken();
|
|
2248
|
-
// Build the HTTP service URL
|
|
2249
|
-
const serviceUrl = this.buildServiceUrl(fullServiceId);
|
|
2196
|
+
this.serviceInfo = await this.server.registerService(this.buildServiceDefinition());
|
|
2197
|
+
// Update overlay and build session
|
|
2198
|
+
const session = await this.updateSession();
|
|
2250
2199
|
if (this.overlay) {
|
|
2251
|
-
this.overlay.setStatus("connected");
|
|
2252
|
-
this.overlay.setInfo({
|
|
2253
|
-
Status: "Connected",
|
|
2254
|
-
Server: this.config.server_url,
|
|
2255
|
-
});
|
|
2256
|
-
this.overlay.setServiceUrl(serviceUrl, sessionToken);
|
|
2257
2200
|
this.overlay.addLog("Service registered", "result");
|
|
2258
2201
|
}
|
|
2259
|
-
console.log(`[hypha-debugger] Connected to ${this.config.server_url}`);
|
|
2260
|
-
console.log(`[hypha-debugger] Service ID: ${fullServiceId}`);
|
|
2261
|
-
console.log(`[hypha-debugger] Service URL: ${serviceUrl}`);
|
|
2262
|
-
console.log(`[hypha-debugger] Token: ${sessionToken}`);
|
|
2263
|
-
console.log(`[hypha-debugger] Test it:\n curl '${serviceUrl}/get_page_info' -H 'Authorization: Bearer ${sessionToken}'`);
|
|
2264
|
-
// Build session object
|
|
2265
|
-
const session = {
|
|
2266
|
-
service_id: fullServiceId,
|
|
2267
|
-
workspace,
|
|
2268
|
-
server: this.server,
|
|
2269
|
-
service_url: serviceUrl,
|
|
2270
|
-
token: sessionToken,
|
|
2271
|
-
destroy: () => this.destroy(),
|
|
2272
|
-
};
|
|
2273
2202
|
// Store globally
|
|
2274
2203
|
w.__HYPHA_DEBUGGER__ = w.__HYPHA_DEBUGGER__ ?? {};
|
|
2275
2204
|
w.__HYPHA_DEBUGGER__.instance = this;
|
|
2276
|
-
w.__HYPHA_DEBUGGER__.session = session;
|
|
2277
2205
|
return session;
|
|
2278
2206
|
}
|
|
2279
2207
|
catch (err) {
|
|
@@ -2305,29 +2233,186 @@ class HyphaDebugger {
|
|
|
2305
2233
|
delete w.__HYPHA_DEBUGGER__.session;
|
|
2306
2234
|
}
|
|
2307
2235
|
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Generate token, build service URL, update overlay instructions, and
|
|
2238
|
+
* return a DebugSession.
|
|
2239
|
+
*/
|
|
2240
|
+
async updateSession(extra) {
|
|
2241
|
+
const fullServiceId = this.serviceInfo?.id ?? this.config.service_id;
|
|
2242
|
+
const sessionToken = await this.server.generateToken();
|
|
2243
|
+
const serviceUrl = this.buildServiceUrl(fullServiceId);
|
|
2244
|
+
const workspace = this.server.config?.workspace ?? "";
|
|
2245
|
+
if (this.overlay) {
|
|
2246
|
+
this.overlay.setStatus("connected");
|
|
2247
|
+
this.overlay.setInfo({
|
|
2248
|
+
Status: "Connected",
|
|
2249
|
+
Server: this.config.server_url,
|
|
2250
|
+
...extra,
|
|
2251
|
+
});
|
|
2252
|
+
this.overlay.setInstructions(this.buildInstructionBlock(serviceUrl, sessionToken));
|
|
2253
|
+
}
|
|
2254
|
+
console.log(`[hypha-debugger] Service URL: ${serviceUrl}`);
|
|
2255
|
+
console.log(`[hypha-debugger] Token: ${sessionToken}`);
|
|
2256
|
+
console.log(`[hypha-debugger] Test:\n curl '${serviceUrl}/get_page_info?_mode=last' -H 'Authorization: Bearer ${sessionToken}'`);
|
|
2257
|
+
const session = {
|
|
2258
|
+
service_id: fullServiceId,
|
|
2259
|
+
workspace,
|
|
2260
|
+
server: this.server,
|
|
2261
|
+
service_url: serviceUrl,
|
|
2262
|
+
token: sessionToken,
|
|
2263
|
+
destroy: () => this.destroy(),
|
|
2264
|
+
};
|
|
2265
|
+
// Always update global session
|
|
2266
|
+
const w = window;
|
|
2267
|
+
w.__HYPHA_DEBUGGER__ = w.__HYPHA_DEBUGGER__ ?? {};
|
|
2268
|
+
w.__HYPHA_DEBUGGER__.session = session;
|
|
2269
|
+
return session;
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Build a stable, predictable service URL.
|
|
2273
|
+
* Strips the clientId prefix so the URL uses only the bare service name.
|
|
2274
|
+
* Callers append ?_mode=last to resolve the most recent instance.
|
|
2275
|
+
*/
|
|
2308
2276
|
buildServiceUrl(serviceId) {
|
|
2309
2277
|
const base = this.config.server_url.replace(/\/+$/, "");
|
|
2310
|
-
// serviceId format: "workspace/clientId:svcName"
|
|
2311
2278
|
const slashIdx = serviceId.indexOf("/");
|
|
2312
2279
|
if (slashIdx !== -1) {
|
|
2313
2280
|
const workspace = serviceId.substring(0, slashIdx);
|
|
2314
2281
|
const svcPart = serviceId.substring(slashIdx + 1);
|
|
2315
|
-
|
|
2282
|
+
// Strip clientId: "abc123:web-debugger" → "web-debugger"
|
|
2283
|
+
const colonIdx = svcPart.indexOf(":");
|
|
2284
|
+
const svcName = colonIdx !== -1 ? svcPart.substring(colonIdx + 1) : svcPart;
|
|
2285
|
+
return `${base}/${workspace}/services/${svcName}`;
|
|
2316
2286
|
}
|
|
2317
2287
|
return `${base}/services/${serviceId}`;
|
|
2318
2288
|
}
|
|
2319
|
-
|
|
2320
|
-
//
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2289
|
+
getHyphaModule() {
|
|
2290
|
+
// Check the static import (works when hypha-rpc is bundled or npm-installed)
|
|
2291
|
+
if (hyphaRpc.connectToServer)
|
|
2292
|
+
return hyphaRpc;
|
|
2293
|
+
// hypha-rpc re-exports under a namespace
|
|
2294
|
+
if (hyphaRpc.hyphaWebsocketClient?.connectToServer)
|
|
2295
|
+
return hyphaRpc.hyphaWebsocketClient;
|
|
2296
|
+
// Fall back to global (when hypha-rpc loaded via separate script tag)
|
|
2324
2297
|
const w = window;
|
|
2325
|
-
if (w.hyphaWebsocketClient?.connectToServer)
|
|
2326
|
-
return w.hyphaWebsocketClient
|
|
2327
|
-
|
|
2328
|
-
throw new Error("hypha-rpc not found. Install it via npm or include the script tag: " +
|
|
2298
|
+
if (w.hyphaWebsocketClient?.connectToServer)
|
|
2299
|
+
return w.hyphaWebsocketClient;
|
|
2300
|
+
throw new Error("hypha-rpc not found. Install it via npm or load it via: " +
|
|
2329
2301
|
'<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.20.97/dist/hypha-rpc-websocket.min.js"></script>');
|
|
2330
2302
|
}
|
|
2303
|
+
getConnectToServer() {
|
|
2304
|
+
return this.getHyphaModule().connectToServer;
|
|
2305
|
+
}
|
|
2306
|
+
buildServiceDefinition() {
|
|
2307
|
+
return {
|
|
2308
|
+
id: this.config.service_id,
|
|
2309
|
+
name: this.config.service_name,
|
|
2310
|
+
type: "debugger",
|
|
2311
|
+
description: "Remote web page debugger. Allows inspecting DOM, taking screenshots, executing JavaScript, and interacting with the page.",
|
|
2312
|
+
config: {
|
|
2313
|
+
visibility: this.config.visibility,
|
|
2314
|
+
},
|
|
2315
|
+
get_page_info: this.wrapFn(getPageInfo, "get_page_info"),
|
|
2316
|
+
get_html: this.wrapFn(getHtml, "get_html"),
|
|
2317
|
+
query_dom: this.wrapFn(queryDom, "query_dom"),
|
|
2318
|
+
click_element: this.wrapFn(clickElement, "click_element"),
|
|
2319
|
+
fill_input: this.wrapFn(fillInput, "fill_input"),
|
|
2320
|
+
scroll_to: this.wrapFn(scrollTo, "scroll_to"),
|
|
2321
|
+
take_screenshot: this.wrapFn(takeScreenshot, "take_screenshot"),
|
|
2322
|
+
execute_script: this.wrapFn(executeScript, "execute_script"),
|
|
2323
|
+
navigate: this.wrapFn(navigate, "navigate"),
|
|
2324
|
+
get_react_tree: this.wrapFn(getReactTree, "get_react_tree"),
|
|
2325
|
+
get_skill_md: this.wrapFn(this.createGetSkillMd(), "get_skill_md"),
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
createGetSkillMd() {
|
|
2329
|
+
const fn = () => {
|
|
2330
|
+
// Build a schema-only map (avoid calling buildServiceDefinition which would recurse)
|
|
2331
|
+
const schemaFns = {};
|
|
2332
|
+
const fns = {
|
|
2333
|
+
get_page_info: getPageInfo, get_html: getHtml,
|
|
2334
|
+
query_dom: queryDom, click_element: clickElement, fill_input: fillInput,
|
|
2335
|
+
scroll_to: scrollTo, take_screenshot: takeScreenshot,
|
|
2336
|
+
execute_script: executeScript, navigate: navigate,
|
|
2337
|
+
get_react_tree: getReactTree,
|
|
2338
|
+
};
|
|
2339
|
+
for (const [name, f] of Object.entries(fns)) {
|
|
2340
|
+
if (f.__schema__)
|
|
2341
|
+
schemaFns[name] = f;
|
|
2342
|
+
}
|
|
2343
|
+
this.serviceInfo
|
|
2344
|
+
? this.buildServiceUrl(this.serviceInfo.id ?? this.config.service_id)
|
|
2345
|
+
: "{SERVICE_URL}";
|
|
2346
|
+
return generateSkillMd(schemaFns);
|
|
2347
|
+
};
|
|
2348
|
+
fn.__schema__ = {
|
|
2349
|
+
name: "getSkillMd",
|
|
2350
|
+
description: "Get the SKILL.md document describing all available debugger functions, their parameters, and usage examples. Follows the agentskills.io specification.",
|
|
2351
|
+
parameters: {
|
|
2352
|
+
type: "object",
|
|
2353
|
+
properties: {},
|
|
2354
|
+
},
|
|
2355
|
+
};
|
|
2356
|
+
return fn;
|
|
2357
|
+
}
|
|
2358
|
+
/** Build the instruction block for the overlay panel. */
|
|
2359
|
+
buildInstructionBlock(serviceUrl, token) {
|
|
2360
|
+
return [
|
|
2361
|
+
`SERVICE_URL="${serviceUrl}"`,
|
|
2362
|
+
`TOKEN="${token}"`,
|
|
2363
|
+
``,
|
|
2364
|
+
`# Quick test:`,
|
|
2365
|
+
`curl "$SERVICE_URL/get_page_info?_mode=last" -H "Authorization: Bearer $TOKEN"`,
|
|
2366
|
+
``,
|
|
2367
|
+
`# Full API docs:`,
|
|
2368
|
+
`curl "$SERVICE_URL/get_skill_md?_mode=last" -H "Authorization: Bearer $TOKEN"`,
|
|
2369
|
+
].join("\n");
|
|
2370
|
+
}
|
|
2371
|
+
/** Wrap a service function with logging and kwargs-to-positional-args support. */
|
|
2372
|
+
wrapFn(fn, name) {
|
|
2373
|
+
const wrapped = async (...args) => {
|
|
2374
|
+
// Hypha's HTTP API calls with keyword arguments (**kwargs),
|
|
2375
|
+
// which arrive on the JS side as a single object argument.
|
|
2376
|
+
// Destructure into positional args based on schema properties.
|
|
2377
|
+
if (args.length === 1 &&
|
|
2378
|
+
args[0] &&
|
|
2379
|
+
typeof args[0] === "object" &&
|
|
2380
|
+
!Array.isArray(args[0]) &&
|
|
2381
|
+
fn.__schema__?.parameters?.properties) {
|
|
2382
|
+
const kwargs = args[0];
|
|
2383
|
+
const props = fn.__schema__.parameters.properties;
|
|
2384
|
+
const paramNames = Object.keys(props);
|
|
2385
|
+
// Check if any kwargs key matches a schema property name
|
|
2386
|
+
const hasMatchingKey = paramNames.some((p) => p in kwargs);
|
|
2387
|
+
if (hasMatchingKey) {
|
|
2388
|
+
args = paramNames.map((p) => kwargs[p]);
|
|
2389
|
+
while (args.length > 0 && args[args.length - 1] === undefined) {
|
|
2390
|
+
args.pop();
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
this.overlay?.addLog(`${name}(${this.summarizeArgs(args)})`, "call");
|
|
2395
|
+
try {
|
|
2396
|
+
const result = await fn(...args);
|
|
2397
|
+
const hasError = result && typeof result === "object" && "error" in result;
|
|
2398
|
+
if (hasError) {
|
|
2399
|
+
this.overlay?.addLog(`${name}: ${result.error}`, "error");
|
|
2400
|
+
}
|
|
2401
|
+
else {
|
|
2402
|
+
this.overlay?.addLog(`${name} -> OK`, "result");
|
|
2403
|
+
}
|
|
2404
|
+
return result;
|
|
2405
|
+
}
|
|
2406
|
+
catch (err) {
|
|
2407
|
+
this.overlay?.addLog(`${name}: ${err.message}`, "error");
|
|
2408
|
+
throw err;
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
if (fn.__schema__) {
|
|
2412
|
+
wrapped.__schema__ = fn.__schema__;
|
|
2413
|
+
}
|
|
2414
|
+
return wrapped;
|
|
2415
|
+
}
|
|
2331
2416
|
summarizeArgs(args) {
|
|
2332
2417
|
if (args.length === 0)
|
|
2333
2418
|
return "";
|
|
@@ -2346,7 +2431,14 @@ class HyphaDebugger {
|
|
|
2346
2431
|
/**
|
|
2347
2432
|
* hypha-debugger - Injectable debugger for web pages, powered by Hypha RPC.
|
|
2348
2433
|
*
|
|
2349
|
-
*
|
|
2434
|
+
* Simplest usage (just include the script tag, auto-starts):
|
|
2435
|
+
* <script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.20.97/dist/hypha-rpc-websocket.min.js"></script>
|
|
2436
|
+
* <script src="https://cdn.jsdelivr.net/npm/hypha-debugger/dist/hypha-debugger.min.js"></script>
|
|
2437
|
+
*
|
|
2438
|
+
* With config via data attributes:
|
|
2439
|
+
* <script src="..." data-server-url="https://hypha.aicell.io" data-service-id="my-debugger"></script>
|
|
2440
|
+
*
|
|
2441
|
+
* Programmatic usage:
|
|
2350
2442
|
* import { startDebugger } from 'hypha-debugger';
|
|
2351
2443
|
* const session = await startDebugger({ server_url: 'https://hypha.aicell.io' });
|
|
2352
2444
|
*/
|
|
@@ -2354,21 +2446,65 @@ class HyphaDebugger {
|
|
|
2354
2446
|
* Start the Hypha debugger. Connects to a Hypha server and registers
|
|
2355
2447
|
* a debug service that remote clients can use to inspect and interact
|
|
2356
2448
|
* with this web page.
|
|
2357
|
-
*
|
|
2358
|
-
* @param config - Configuration for the debugger.
|
|
2359
|
-
* @returns A session object with service_id, workspace, and destroy().
|
|
2360
|
-
*
|
|
2361
|
-
* @example
|
|
2362
|
-
* ```js
|
|
2363
|
-
* import { startDebugger } from 'hypha-debugger';
|
|
2364
|
-
* const session = await startDebugger({ server_url: 'https://hypha.aicell.io' });
|
|
2365
|
-
* console.log(`Service: ${session.service_id}, Workspace: ${session.workspace}`);
|
|
2366
|
-
* ```
|
|
2367
2449
|
*/
|
|
2368
2450
|
async function startDebugger(config) {
|
|
2369
2451
|
const debugger_ = new HyphaDebugger(config);
|
|
2370
2452
|
return debugger_.start();
|
|
2371
2453
|
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Auto-start: when loaded via <script> tag, automatically start the debugger.
|
|
2456
|
+
* Configuration can be provided via data-* attributes on the script tag:
|
|
2457
|
+
* data-server-url, data-workspace, data-token, data-service-id, data-no-ui
|
|
2458
|
+
*
|
|
2459
|
+
* Set data-manual to disable auto-start.
|
|
2460
|
+
*/
|
|
2461
|
+
function autoStart() {
|
|
2462
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
2463
|
+
return;
|
|
2464
|
+
// Find our own script tag
|
|
2465
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
2466
|
+
let scriptEl = null;
|
|
2467
|
+
for (const s of Array.from(scripts)) {
|
|
2468
|
+
if (s.src && s.src.includes("hypha-debugger")) {
|
|
2469
|
+
scriptEl = s;
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
// Skip if data-manual is set
|
|
2474
|
+
if (scriptEl?.hasAttribute("data-manual"))
|
|
2475
|
+
return;
|
|
2476
|
+
// Skip if already started
|
|
2477
|
+
if (window.__HYPHA_DEBUGGER__?.instance)
|
|
2478
|
+
return;
|
|
2479
|
+
const serverUrl = scriptEl?.getAttribute("data-server-url") ?? "https://hypha.aicell.io";
|
|
2480
|
+
const config = {
|
|
2481
|
+
server_url: serverUrl,
|
|
2482
|
+
};
|
|
2483
|
+
if (scriptEl?.getAttribute("data-workspace")) {
|
|
2484
|
+
config.workspace = scriptEl.getAttribute("data-workspace");
|
|
2485
|
+
}
|
|
2486
|
+
if (scriptEl?.getAttribute("data-token")) {
|
|
2487
|
+
config.token = scriptEl.getAttribute("data-token");
|
|
2488
|
+
}
|
|
2489
|
+
if (scriptEl?.getAttribute("data-service-id")) {
|
|
2490
|
+
config.service_id = scriptEl.getAttribute("data-service-id");
|
|
2491
|
+
}
|
|
2492
|
+
if (scriptEl?.hasAttribute("data-no-ui")) {
|
|
2493
|
+
config.show_ui = false;
|
|
2494
|
+
}
|
|
2495
|
+
startDebugger(config).catch((err) => {
|
|
2496
|
+
console.error("[hypha-debugger] Auto-start failed:", err);
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
// Run auto-start after DOM is ready
|
|
2500
|
+
if (typeof window !== "undefined") {
|
|
2501
|
+
if (document.readyState === "loading") {
|
|
2502
|
+
document.addEventListener("DOMContentLoaded", autoStart);
|
|
2503
|
+
}
|
|
2504
|
+
else {
|
|
2505
|
+
autoStart();
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2372
2508
|
|
|
2373
2509
|
export { HyphaDebugger, startDebugger };
|
|
2374
2510
|
//# sourceMappingURL=hypha-debugger.mjs.map
|