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.
@@ -47,8 +47,8 @@ const overlayStyles = `
47
47
 
48
48
  .debugger-panel {
49
49
  display: none;
50
- width: 320px;
51
- max-height: 420px;
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: 340px;
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
- .url-section {
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
- .url-row {
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: #89b4fa;
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
- overflow: hidden;
178
- display: -webkit-box;
179
- -webkit-line-clamp: 2;
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
- this.infoBody.innerHTML = "";
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.appendChild(row);
396
+ this.infoBody.insertBefore(row, firstChild);
392
397
  }
393
398
  }
394
- /** Show the service URL with a copy button. */
395
- setServiceUrl(url, token) {
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 = "url-section";
398
- const urlLabel = document.createElement("div");
399
- urlLabel.className = "url-label";
400
- urlLabel.textContent = "Service URL";
401
- section.appendChild(urlLabel);
402
- const urlRow = document.createElement("div");
403
- urlRow.className = "url-row";
404
- const urlText = document.createElement("span");
405
- urlText.className = "url-text";
406
- urlText.textContent = url;
407
- urlText.title = url;
408
- const copyBtn = document.createElement("button");
409
- copyBtn.className = "copy-btn";
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
- tokenRow.appendChild(tokenText);
441
- tokenRow.appendChild(copyTokenBtn);
442
- section.appendChild(tokenRow);
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
- return collectPageInfo();
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
- level: {
563
- type: "string",
564
- description: 'Filter by log level: "log", "warn", "error", "info". Omit for all levels.',
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
- limit: {
543
+ log_limit: {
568
544
  type: "number",
569
- description: "Maximum number of log entries to return (most recent). Default: 100.",
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, options) {
638
- const limit = options?.limit ?? 20;
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 getComputedStyles(selector, properties) {
774
- const el = document.querySelector(selector);
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 computed = getComputedStyle(el);
779
- const result = {};
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
- bounds: {
830
- x: Math.round(rect.x),
831
- y: Math.round(rect.y),
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
- getElementBounds.__schema__ = {
842
- name: "getElementBounds",
843
- description: "Get the bounding rectangle and visibility of a DOM element.",
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(options) {
1741
- const selector = options?.selector;
1742
- const format = options?.format ?? "png";
1743
- const quality = options?.quality ?? 0.92;
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: scale,
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 (format === "jpeg") {
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 * scale);
1771
- let height = Math.round(rect.height * scale);
1704
+ let width = Math.round(rect.width * scl);
1705
+ let height = Math.round(rect.height * scl);
1772
1706
  // Optionally resize if too large
1773
- const maxW = options?.max_width;
1774
- const maxH = options?.max_height;
1775
- if (maxW && width > maxW) {
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 (maxH && height > maxH) {
1781
- const ratio = maxH / height;
1782
- height = maxH;
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, options) {
1835
- const timeoutMs = options?.timeout_ms ?? 10000;
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(options) {
2100
- const selector = options?.selector ?? "#root";
2101
- const maxDepth = options?.max_depth ?? 5;
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
- const service = {
2218
- id: this.config.service_id,
2219
- name: this.config.service_name,
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
- return `${base}/${workspace}/services/${svcPart}`;
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
- getConnectToServer() {
2320
- // Prefer the static import; fall back to global for UMD/script-tag usage
2321
- const connect = hyphaRpc.connectToServer;
2322
- if (connect)
2323
- return connect;
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.connectToServer;
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
- * Usage:
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