@anubhav-choubey/web-for-agents 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,16 @@ import {
2
2
  collapseWhitespace
3
3
  } from "./chunk-FEAKVNMG.js";
4
4
  import {
5
+ ACTION_ATTR,
6
+ ACTION_DESCRIPTION_ATTR,
7
+ ACTION_DESTRUCTIVE_ATTR,
5
8
  CONTENT_ATTR,
6
9
  EXCLUDE_ATTR,
7
10
  HIDDEN_SELECTOR,
11
+ WEBMCP_TOOLDESCRIPTION_ATTR,
12
+ WEBMCP_TOOLNAME_ATTR,
8
13
  resolveUrl
9
- } from "./chunk-AZG5MLNG.js";
14
+ } from "./chunk-F7PN5R25.js";
10
15
 
11
16
  // src/shared/shortcut.ts
12
17
  var DEFAULT_SHORTCUT = "alt+shift+a";
@@ -37,6 +42,60 @@ function matchesShortcut(e, s) {
37
42
  return e.code === s.code && e.altKey === s.alt && e.shiftKey === s.shift && e.ctrlKey === s.ctrl && e.metaKey === s.meta;
38
43
  }
39
44
 
45
+ // src/shared/content.ts
46
+ var DEFAULT_CONTENT_SELECTOR = "main";
47
+ var STRIP = `script, style, noscript, template, nav, [${EXCLUDE_ATTR}], ${HIDDEN_SELECTOR}`;
48
+ var BODY_STRIP = "header, nav, footer, aside";
49
+ function selectContentRegion(doc, opts = {}) {
50
+ const element = doc.createElement("div");
51
+ const take = (n) => opts.inPlace ? n : n.cloneNode(true);
52
+ let source;
53
+ const markers = Array.from(doc.querySelectorAll(`[${CONTENT_ATTR}]`)).filter(
54
+ (el) => !el.parentElement?.closest(`[${CONTENT_ATTR}]`)
55
+ );
56
+ let selected = null;
57
+ if (markers.length > 0) {
58
+ source = "marker";
59
+ for (const m of markers) element.appendChild(take(m));
60
+ } else {
61
+ try {
62
+ selected = doc.querySelector(opts.contentSelector ?? DEFAULT_CONTENT_SELECTOR);
63
+ } catch {
64
+ selected = null;
65
+ }
66
+ if (selected) {
67
+ source = "selector";
68
+ element.appendChild(take(selected));
69
+ } else {
70
+ source = "body";
71
+ for (const child of Array.from(doc.body?.childNodes ?? [])) {
72
+ element.appendChild(take(child));
73
+ }
74
+ for (const el of Array.from(element.querySelectorAll(BODY_STRIP))) el.remove();
75
+ }
76
+ }
77
+ for (const el of Array.from(element.querySelectorAll(STRIP))) el.remove();
78
+ return { element, source };
79
+ }
80
+
81
+ // src/shared/pageMeta.ts
82
+ function readPageMeta(doc) {
83
+ const titleTag = (doc.title ?? "").trim();
84
+ const title = titleTag || (doc.querySelector("h1")?.textContent ?? "").trim();
85
+ const description = doc.querySelector('meta[name="description"]')?.getAttribute("content")?.trim() || null;
86
+ const robots = doc.querySelector('meta[name="robots"]')?.getAttribute("content") ?? null;
87
+ const canonical = doc.querySelector('link[rel~="canonical"]')?.getAttribute("href")?.trim() || null;
88
+ return {
89
+ title: title || null,
90
+ titleFromDocument: title !== "",
91
+ titleTag: titleTag !== "",
92
+ description,
93
+ robots,
94
+ noindex: /noindex/i.test(robots ?? ""),
95
+ canonical
96
+ };
97
+ }
98
+
40
99
  // src/shared/markdown.ts
41
100
  import TurndownService from "turndown";
42
101
  import { gfm } from "turndown-plugin-gfm";
@@ -108,68 +167,45 @@ function buildPageMarkdown(title, contentHtml, pageUrl) {
108
167
  ${body}`.replace(/\n+$/, "\n");
109
168
  }
110
169
 
111
- // src/shared/content.ts
112
- var DEFAULT_CONTENT_SELECTOR = "main";
113
- var STRIP = `script, style, noscript, template, nav, [${EXCLUDE_ATTR}], ${HIDDEN_SELECTOR}`;
114
- var BODY_STRIP = "header, nav, footer, aside";
115
- function selectContentRegion(doc, opts = {}) {
116
- const element = doc.createElement("div");
117
- const take = (n) => opts.inPlace ? n : n.cloneNode(true);
118
- let source;
119
- const markers = Array.from(doc.querySelectorAll(`[${CONTENT_ATTR}]`)).filter(
120
- (el) => !el.parentElement?.closest(`[${CONTENT_ATTR}]`)
121
- );
122
- let selected = null;
123
- if (markers.length > 0) {
124
- source = "marker";
125
- for (const m of markers) element.appendChild(take(m));
126
- } else {
127
- try {
128
- selected = doc.querySelector(opts.contentSelector ?? DEFAULT_CONTENT_SELECTOR);
129
- } catch {
130
- selected = null;
131
- }
132
- if (selected) {
133
- source = "selector";
134
- element.appendChild(take(selected));
135
- } else {
136
- source = "body";
137
- for (const child of Array.from(doc.body?.childNodes ?? [])) {
138
- element.appendChild(take(child));
139
- }
140
- for (const el of Array.from(element.querySelectorAll(BODY_STRIP))) el.remove();
141
- }
142
- }
143
- for (const el of Array.from(element.querySelectorAll(STRIP))) el.remove();
144
- return { element, source };
145
- }
146
-
147
- // src/shared/pageMeta.ts
148
- function readPageMeta(doc) {
149
- const titleTag = (doc.title ?? "").trim();
150
- const title = titleTag || (doc.querySelector("h1")?.textContent ?? "").trim();
151
- const description = doc.querySelector('meta[name="description"]')?.getAttribute("content")?.trim() || null;
152
- const robots = doc.querySelector('meta[name="robots"]')?.getAttribute("content") ?? null;
153
- const canonical = doc.querySelector('link[rel~="canonical"]')?.getAttribute("href")?.trim() || null;
154
- return {
155
- title: title || null,
156
- titleFromDocument: title !== "",
157
- titleTag: titleTag !== "",
158
- description,
159
- robots,
160
- noindex: /noindex/i.test(robots ?? ""),
161
- canonical
162
- };
170
+ // src/shared/markupActions.ts
171
+ function markupActions(doc) {
172
+ const out = [];
173
+ doc.querySelectorAll(`[${ACTION_ATTR}]`).forEach((el) => {
174
+ const name = el.getAttribute(ACTION_ATTR)?.trim();
175
+ if (!name) return;
176
+ out.push({
177
+ element: el,
178
+ name,
179
+ description: el.getAttribute(ACTION_DESCRIPTION_ATTR) || null,
180
+ kind: el.tagName === "FORM" ? "form" : "click",
181
+ destructive: el.getAttribute(ACTION_DESTRUCTIVE_ATTR) === "true",
182
+ source: "markup"
183
+ });
184
+ });
185
+ doc.querySelectorAll(`form[${WEBMCP_TOOLNAME_ATTR}]`).forEach((el) => {
186
+ const name = el.getAttribute(WEBMCP_TOOLNAME_ATTR)?.trim();
187
+ if (!name) return;
188
+ out.push({
189
+ element: el,
190
+ name,
191
+ description: el.getAttribute(WEBMCP_TOOLDESCRIPTION_ATTR) || null,
192
+ kind: "form",
193
+ destructive: false,
194
+ source: "webmcp-declarative"
195
+ });
196
+ });
197
+ return out;
163
198
  }
164
199
 
165
200
  export {
166
- escapeMarkdownInline,
167
- escapeLinkTarget,
168
- buildPageMarkdown,
169
201
  DEFAULT_CONTENT_SELECTOR,
170
202
  selectContentRegion,
171
203
  DEFAULT_SHORTCUT,
172
204
  parseShortcut,
173
205
  matchesShortcut,
174
- readPageMeta
206
+ readPageMeta,
207
+ escapeMarkdownInline,
208
+ escapeLinkTarget,
209
+ buildPageMarkdown,
210
+ markupActions
175
211
  };
@@ -1,19 +1,23 @@
1
1
  import {
2
2
  READY_GLOBAL
3
- } from "./chunk-AZG5MLNG.js";
3
+ } from "./chunk-F7PN5R25.js";
4
4
  import {
5
5
  POLL_MS,
6
6
  RenderError,
7
7
  SERIALIZE_EXPR,
8
8
  SETTLED_POLL_MS,
9
+ SNAPSHOT_GRACE_MS,
10
+ TimeoutError,
11
+ assertNoPageErrors,
9
12
  blockedHint,
10
13
  createDeadline,
11
14
  createOriginPolicy,
12
15
  readyTimeoutMessage,
13
16
  runInPage,
17
+ snapshotWarning,
14
18
  waitForReady,
15
19
  withoutBlockedLoads
16
- } from "./chunk-5TNG4QMP.js";
20
+ } from "./chunk-PGZH33B5.js";
17
21
  import {
18
22
  LOG_PREFIX,
19
23
  errorMessage
@@ -56,8 +60,11 @@ var PROBE_SCRIPT = `(function () {
56
60
  };
57
61
  }
58
62
  if (w.MutationObserver) {
63
+ // Content changes only: attribute churn (class, style, aria-*, data-* animations) never settles
64
+ // on an animated page. Attributes are still saved, so a late attribute-only update (a meta tag or
65
+ // an img src changed alone, after the content is quiet) may be captured before or after it lands.
59
66
  new w.MutationObserver(touch).observe(document, {
60
- childList: true, subtree: true, attributes: true, characterData: true
67
+ childList: true, subtree: true, characterData: true
61
68
  });
62
69
  }
63
70
  w.addEventListener("error", function (e) {
@@ -74,12 +81,25 @@ var PROBE_SCRIPT = `(function () {
74
81
  p.errors.push(String((r && r.message) || r || "unhandledrejection")); touch();
75
82
  });
76
83
  })();`;
77
- function readyCheck(idleMs = IDLE_MS) {
78
- return `(function () {
84
+ function readyCheck(idleMs = IDLE_MS, readySelector) {
85
+ if (readySelector === void 0) {
86
+ return `(function () {
79
87
  var p = window.${PROBE_GLOBAL};
80
88
  if (!p) return { ready: false, errors: [], path: location.pathname };
81
89
  var ready = document.readyState === "complete" && p.inflight === 0 && Date.now() - p.last >= ${idleMs} && window.${READY_GLOBAL} !== false;
82
90
  return { ready: ready, errors: p.errors.slice(), path: location.pathname };
91
+ })()`;
92
+ }
93
+ const sel = JSON.stringify(readySelector);
94
+ const invalid = JSON.stringify(`prerender.readySelector ${sel} is not a valid CSS selector: `);
95
+ return `(function () {
96
+ var p = window.${PROBE_GLOBAL};
97
+ if (!p) return { ready: false, errors: [], path: location.pathname };
98
+ var found;
99
+ try { found = document.querySelector(${sel}) !== null; }
100
+ catch (e) { return { ready: false, errors: [${invalid} + String((e && e.message) || e)], path: location.pathname }; }
101
+ var ready = document.readyState === "complete" && p.inflight === 0 && found && window.${READY_GLOBAL} !== false;
102
+ return { ready: ready, errors: p.errors.slice(), path: location.pathname };
83
103
  })()`;
84
104
  }
85
105
  var READY_CHECK = readyCheck(IDLE_MS);
@@ -250,6 +270,24 @@ function createHappyDomRenderer(opts = {}) {
250
270
  }
251
271
  }
252
272
  });
273
+ const waitUntilReady = async (page, blocked, deadline, timeoutMs) => {
274
+ const poll = async (check2) => {
275
+ const r = page.evaluate(check2);
276
+ return { ready: r.ready, errors: withoutBlockedLoads([...r.errors], blocked) };
277
+ };
278
+ if (opts.readySelector !== void 0) {
279
+ const check2 = readyCheck(IDLE_MS, opts.readySelector);
280
+ return waitForReady(() => poll(check2), timeoutMs, POLL_MS);
281
+ }
282
+ let timer;
283
+ const settled = await Promise.race([
284
+ page.waitUntilComplete().then(() => true),
285
+ new Promise((r) => timer = setTimeout(() => r(false), Math.min(deadline.remaining(), 2e3)))
286
+ ]).finally(() => clearTimeout(timer));
287
+ const idleMs = opts.idleMs ?? (settled ? SETTLED_IDLE_MS : IDLE_MS);
288
+ const check = readyCheck(idleMs);
289
+ return waitForReady(() => poll(check), timeoutMs, Math.min(settled ? SETTLED_POLL_MS : POLL_MS, idleMs));
290
+ };
253
291
  return {
254
292
  name: "happy-dom",
255
293
  async render(url, timeoutMs, inPage) {
@@ -265,48 +303,42 @@ function createHappyDomRenderer(opts = {}) {
265
303
  };
266
304
  const sink = pageSink(page);
267
305
  attach(sink);
268
- const step = (work) => deadline.race(Promise.race([work, sink.failed]), timeoutText);
306
+ const within = (d, text, work) => d.race(Promise.race([work, sink.failed]), text);
307
+ const step = (work) => within(deadline, timeoutText, work);
308
+ let timedOut = false;
269
309
  try {
270
310
  try {
271
311
  await step(page.goto(url));
272
- let timer;
273
- const settled = await step(
274
- Promise.race([
275
- page.waitUntilComplete().then(() => true),
276
- new Promise((r) => timer = setTimeout(() => r(false), Math.min(deadline.remaining(), 2e3)))
277
- ]).finally(() => clearTimeout(timer))
278
- );
279
- const idleMs = opts.idleMs ?? (settled ? SETTLED_IDLE_MS : IDLE_MS);
280
- const check = readyCheck(idleMs);
281
- await step(
282
- waitForReady(
283
- async () => {
284
- const r = page.evaluate(check);
285
- return { ready: r.ready, errors: withoutBlockedLoads([...r.errors], state.blocked) };
286
- },
287
- timeoutMs,
288
- Math.min(settled ? SETTLED_POLL_MS : POLL_MS, idleMs)
289
- )
290
- );
312
+ try {
313
+ await step(waitUntilReady(page, state.blocked, deadline, timeoutMs));
314
+ } catch (e) {
315
+ if (!(opts.onTimeout === "snapshot" && e instanceof TimeoutError)) throw e;
316
+ timedOut = true;
317
+ await assertNoPageErrors(() => {
318
+ const r = page.evaluate(readyCheck(IDLE_MS, opts.readySelector));
319
+ return withoutBlockedLoads([...r.errors], state.blocked);
320
+ });
321
+ }
291
322
  } catch (e) {
292
323
  throw new RenderError("happy-dom", errorMessage(e) + blockedHint(state.blocked) + syncHint(state.syncRefused));
293
324
  }
294
325
  const html = page.evaluate(SERIALIZE_EXPR);
295
326
  const finalPath = new URL(page.url).pathname;
327
+ const after = timedOut ? createDeadline(SNAPSHOT_GRACE_MS) : deadline;
328
+ const afterMs = timedOut ? SNAPSHOT_GRACE_MS : timeoutMs;
296
329
  const results = await runInPage(
297
330
  "happy-dom",
298
331
  inPage,
299
- (expr) => step(Promise.resolve().then(() => page.evaluate(expr))),
300
- deadline,
301
- timeoutMs
332
+ (expr) => within(after, readyTimeoutMessage(afterMs), Promise.resolve().then(() => page.evaluate(expr))),
333
+ after,
334
+ afterMs
302
335
  );
303
- return {
304
- html,
305
- finalPath,
306
- inPage: results,
336
+ const warnings = [
337
+ ...timedOut ? [snapshotWarning(timeoutMs)] : [],
307
338
  // The page rendered anyway, but without what the refused requests would have loaded.
308
- ...state.syncRefused.length > 0 ? { warnings: [...new Map(state.syncRefused.map((r) => [r.kind + r.url, r])).values()].map(syncRefusalText) } : {}
309
- };
339
+ ...[...new Map(state.syncRefused.map((r) => [r.kind + r.url, r])).values()].map(syncRefusalText)
340
+ ];
341
+ return { html, finalPath, inPage: results, ...warnings.length > 0 ? { warnings } : {} };
310
342
  } finally {
311
343
  active.delete(state);
312
344
  await page.close().catch(() => {
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@ import {
2
2
  audit,
3
3
  formatText,
4
4
  runCli
5
- } from "./chunk-IF32TPGR.js";
6
- import "./chunk-CKXHTGGT.js";
7
- import "./chunk-LCVI2PRP.js";
5
+ } from "./chunk-P6JSPAA2.js";
6
+ import "./chunk-CGIIB6AF.js";
7
+ import "./chunk-PHTV5NAY.js";
8
8
  import "./chunk-LCLDQ3A3.js";
9
9
  import "./chunk-FEAKVNMG.js";
10
- import "./chunk-AZG5MLNG.js";
10
+ import "./chunk-F7PN5R25.js";
11
11
  import "./chunk-JJN6FNJD.js";
12
12
  export {
13
13
  audit,
@@ -2,9 +2,9 @@ import {
2
2
  createHappyDomRenderer,
3
3
  syncHint,
4
4
  syncRefusalText
5
- } from "./chunk-HONWT6GK.js";
6
- import "./chunk-AZG5MLNG.js";
7
- import "./chunk-5TNG4QMP.js";
5
+ } from "./chunk-SMFRAZAA.js";
6
+ import "./chunk-F7PN5R25.js";
7
+ import "./chunk-PGZH33B5.js";
8
8
  import "./chunk-JJN6FNJD.js";
9
9
  export {
10
10
  createHappyDomRenderer,