@anubhav-choubey/web-for-agents 0.1.0 → 0.3.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.
@@ -1,12 +1,21 @@
1
1
  import {
2
- collapseWhitespace
3
- } from "./chunk-FEAKVNMG.js";
2
+ ACTION_NAME_RULE,
3
+ collapseWhitespace,
4
+ isValidActionName
5
+ } from "./chunk-4STTPCBR.js";
4
6
  import {
7
+ ACTION_ATTR,
8
+ ACTION_DESCRIPTION_ATTR,
9
+ ACTION_DESTRUCTIVE_ATTR,
5
10
  CONTENT_ATTR,
6
11
  EXCLUDE_ATTR,
7
12
  HIDDEN_SELECTOR,
8
- resolveUrl
9
- } from "./chunk-AZG5MLNG.js";
13
+ LINK_ACTION_SELECTOR,
14
+ WEBMCP_TOOLDESCRIPTION_ATTR,
15
+ WEBMCP_TOOLNAME_ATTR,
16
+ resolveUrl,
17
+ tryResolveUrl
18
+ } from "./chunk-2Y3LLYEC.js";
10
19
 
11
20
  // src/shared/shortcut.ts
12
21
  var DEFAULT_SHORTCUT = "alt+shift+a";
@@ -37,6 +46,143 @@ function matchesShortcut(e, s) {
37
46
  return e.code === s.code && e.altKey === s.alt && e.shiftKey === s.shift && e.ctrlKey === s.ctrl && e.metaKey === s.meta;
38
47
  }
39
48
 
49
+ // src/shared/markupActions.ts
50
+ function markupActions(doc) {
51
+ const out = [];
52
+ doc.querySelectorAll(`[${ACTION_ATTR}]`).forEach((el) => {
53
+ const name = el.getAttribute(ACTION_ATTR)?.trim();
54
+ if (!name) return;
55
+ const link = el.matches(LINK_ACTION_SELECTOR);
56
+ out.push({
57
+ element: el,
58
+ name,
59
+ description: el.getAttribute(ACTION_DESCRIPTION_ATTR) || null,
60
+ kind: el.tagName === "FORM" ? "form" : link ? "link" : "click",
61
+ destructive: el.getAttribute(ACTION_DESTRUCTIVE_ATTR) === "true",
62
+ source: "markup",
63
+ ...link ? { href: el.getAttribute("href") ?? "" } : {}
64
+ });
65
+ });
66
+ doc.querySelectorAll(`form[${WEBMCP_TOOLNAME_ATTR}]`).forEach((el) => {
67
+ const name = el.getAttribute(WEBMCP_TOOLNAME_ATTR)?.trim();
68
+ if (!name) return;
69
+ out.push({
70
+ element: el,
71
+ name,
72
+ description: el.getAttribute(WEBMCP_TOOLDESCRIPTION_ATTR) || null,
73
+ kind: "form",
74
+ destructive: false,
75
+ source: "webmcp-declarative"
76
+ });
77
+ });
78
+ return out;
79
+ }
80
+ function markupLinks(doc) {
81
+ return markupActions(doc).flatMap(
82
+ (a) => a.kind === "link" ? [{ name: a.name, description: a.description, destructive: a.destructive, href: a.href ?? "" }] : []
83
+ );
84
+ }
85
+ var LINK_ACTION_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
86
+ function resolveLinkHref(href, pageUrl) {
87
+ const url = tryResolveUrl(href, pageUrl);
88
+ if (url === null) return { ok: false, reason: "its href is not a valid URL" };
89
+ const protocol = new URL(url).protocol;
90
+ if (!LINK_ACTION_PROTOCOLS.includes(protocol)) {
91
+ return { ok: false, reason: `${protocol} links are not allowed (only http(s), mailto: and tel:)` };
92
+ }
93
+ return { ok: true, href: url };
94
+ }
95
+ var INVALID_NAME_REASON = `invalid name (${ACTION_NAME_RULE})`;
96
+ function firstPerName(...groups) {
97
+ const seen = /* @__PURE__ */ new Set();
98
+ return groups.flat().filter((a) => !seen.has(a.name) && !!seen.add(a.name));
99
+ }
100
+ function actionGroups(markup) {
101
+ return [markup.filter((a) => a.kind !== "link"), markup.filter((a) => a.kind === "link")];
102
+ }
103
+ function linkActions(links, opts, warn) {
104
+ const seen = new Set(opts.taken);
105
+ const out = [];
106
+ for (const link of links) {
107
+ if (seen.has(link.name)) continue;
108
+ seen.add(link.name);
109
+ if (!isValidActionName(link.name)) {
110
+ warn(`link action "${link.name}" on ${opts.page} skipped: ${INVALID_NAME_REASON}`);
111
+ continue;
112
+ }
113
+ const r = resolveLinkHref(link.href, opts.pageUrl);
114
+ if (!r.ok) {
115
+ warn(`link action "${link.name}" on ${opts.page} skipped: ${r.reason}`);
116
+ continue;
117
+ }
118
+ out.push({
119
+ name: link.name,
120
+ description: link.description?.trim() || link.name,
121
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
122
+ destructive: link.destructive,
123
+ readOnly: !link.destructive,
124
+ kind: "link",
125
+ href: r.href,
126
+ .../^https?:$/.test(new URL(r.href).protocol) ? { method: "GET" } : {}
127
+ });
128
+ }
129
+ return out;
130
+ }
131
+
132
+ // src/shared/content.ts
133
+ var DEFAULT_CONTENT_SELECTOR = "main";
134
+ var STRIP = `script, style, noscript, template, nav, [${EXCLUDE_ATTR}], ${HIDDEN_SELECTOR}`;
135
+ var BODY_STRIP = "header, nav, footer, aside";
136
+ function selectContentRegion(doc, opts = {}) {
137
+ const element = doc.createElement("div");
138
+ const take = (n) => opts.inPlace ? n : n.cloneNode(true);
139
+ let source;
140
+ const markers = Array.from(doc.querySelectorAll(`[${CONTENT_ATTR}]`)).filter(
141
+ (el) => !el.parentElement?.closest(`[${CONTENT_ATTR}]`)
142
+ );
143
+ let selected = null;
144
+ if (markers.length > 0) {
145
+ source = "marker";
146
+ for (const m of markers) element.appendChild(take(m));
147
+ } else {
148
+ try {
149
+ selected = doc.querySelector(opts.contentSelector ?? DEFAULT_CONTENT_SELECTOR);
150
+ } catch {
151
+ selected = null;
152
+ }
153
+ if (selected) {
154
+ source = "selector";
155
+ element.appendChild(take(selected));
156
+ } else {
157
+ source = "body";
158
+ for (const child of Array.from(doc.body?.childNodes ?? [])) {
159
+ element.appendChild(take(child));
160
+ }
161
+ for (const el of Array.from(element.querySelectorAll(BODY_STRIP))) el.remove();
162
+ }
163
+ }
164
+ for (const el of Array.from(element.querySelectorAll(STRIP))) el.remove();
165
+ return { element, source };
166
+ }
167
+
168
+ // src/shared/pageMeta.ts
169
+ function readPageMeta(doc) {
170
+ const titleTag = (doc.title ?? "").trim();
171
+ const title = titleTag || (doc.querySelector("h1")?.textContent ?? "").trim();
172
+ const description = doc.querySelector('meta[name="description"]')?.getAttribute("content")?.trim() || null;
173
+ const robots = doc.querySelector('meta[name="robots"]')?.getAttribute("content") ?? null;
174
+ const canonical = doc.querySelector('link[rel~="canonical"]')?.getAttribute("href")?.trim() || null;
175
+ return {
176
+ title: title || null,
177
+ titleFromDocument: title !== "",
178
+ titleTag: titleTag !== "",
179
+ description,
180
+ robots,
181
+ noindex: /noindex/i.test(robots ?? ""),
182
+ canonical
183
+ };
184
+ }
185
+
40
186
  // src/shared/markdown.ts
41
187
  import TurndownService from "turndown";
42
188
  import { gfm } from "turndown-plugin-gfm";
@@ -108,68 +254,21 @@ function buildPageMarkdown(title, contentHtml, pageUrl) {
108
254
  ${body}`.replace(/\n+$/, "\n");
109
255
  }
110
256
 
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
- };
163
- }
164
-
165
257
  export {
166
- escapeMarkdownInline,
167
- escapeLinkTarget,
168
- buildPageMarkdown,
258
+ markupActions,
259
+ markupLinks,
260
+ resolveLinkHref,
261
+ INVALID_NAME_REASON,
262
+ firstPerName,
263
+ actionGroups,
264
+ linkActions,
169
265
  DEFAULT_CONTENT_SELECTOR,
170
266
  selectContentRegion,
171
267
  DEFAULT_SHORTCUT,
172
268
  parseShortcut,
173
269
  matchesShortcut,
174
- readPageMeta
270
+ readPageMeta,
271
+ escapeMarkdownInline,
272
+ escapeLinkTarget,
273
+ buildPageMarkdown
175
274
  };
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";
8
- import "./chunk-LCLDQ3A3.js";
9
- import "./chunk-FEAKVNMG.js";
10
- import "./chunk-AZG5MLNG.js";
5
+ } from "./chunk-357ATBMS.js";
6
+ import "./chunk-CGIIB6AF.js";
7
+ import "./chunk-L3AYWCSX.js";
8
+ import "./chunk-MIVGE5TM.js";
9
+ import "./chunk-4STTPCBR.js";
10
+ import "./chunk-2Y3LLYEC.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-K3B53RIO.js";
6
+ import "./chunk-2Y3LLYEC.js";
7
+ import "./chunk-PGZH33B5.js";
8
8
  import "./chunk-JJN6FNJD.js";
9
9
  export {
10
10
  createHappyDomRenderer,
package/dist/index.d.ts CHANGED
@@ -60,12 +60,23 @@ type JsonLdNode = {
60
60
  "@type": string;
61
61
  [k: string]: unknown;
62
62
  };
63
+ /** A single `@graph` document: one `@context`, many nodes. Built by `schema.graph`. */
64
+ type JsonLdGraph = {
65
+ "@context": "https://schema.org";
66
+ "@graph": JsonLdNode[];
67
+ };
63
68
  /** JSON-LD as a string that is safe inside a `<script>` element (`<`, `>`, `&`, U+2028 and U+2029 are escaped). */
64
- declare function serializeJsonLd(data: JsonLdNode | JsonLdNode[]): string;
69
+ declare function serializeJsonLd(data: JsonLdNode | JsonLdNode[] | JsonLdGraph): string;
65
70
  type Author = {
66
71
  name: string;
67
72
  url?: string;
68
73
  };
74
+ type Offer = {
75
+ price: number | string;
76
+ priceCurrency: string;
77
+ availability?: "InStock" | "OutOfStock" | "PreOrder";
78
+ url?: string;
79
+ };
69
80
  /** Builders for typed schema.org JSON-LD nodes. Undefined fields are omitted. */
70
81
  declare const schema: {
71
82
  article(i: {
@@ -83,12 +94,7 @@ declare const schema: {
83
94
  image?: string | string[];
84
95
  sku?: string;
85
96
  brand?: string;
86
- offers?: {
87
- price: number | string;
88
- priceCurrency: string;
89
- availability?: "InStock" | "OutOfStock" | "PreOrder";
90
- url?: string;
91
- };
97
+ offers?: Offer;
92
98
  }): JsonLdNode;
93
99
  faqPage(items: Array<{
94
100
  question: string;
@@ -109,11 +115,64 @@ declare const schema: {
109
115
  url: string;
110
116
  searchUrlTemplate?: string;
111
117
  }): JsonLdNode;
118
+ person(i: {
119
+ name: string;
120
+ url?: string;
121
+ image?: string;
122
+ jobTitle?: string;
123
+ description?: string;
124
+ email?: string;
125
+ sameAs?: string[];
126
+ worksFor?: string | {
127
+ name: string;
128
+ url?: string;
129
+ };
130
+ }): JsonLdNode;
131
+ softwareApplication(i: {
132
+ name: string;
133
+ description?: string;
134
+ applicationCategory?: string;
135
+ operatingSystem?: string;
136
+ url?: string;
137
+ image?: string | string[];
138
+ offers?: Offer;
139
+ aggregateRating?: {
140
+ ratingValue: number | string;
141
+ ratingCount?: number;
142
+ reviewCount?: number;
143
+ bestRating?: number;
144
+ };
145
+ author?: Author;
146
+ }): JsonLdNode;
147
+ profilePage(i: {
148
+ mainEntity: JsonLdNode;
149
+ url?: string;
150
+ name?: string;
151
+ dateCreated?: string;
152
+ dateModified?: string;
153
+ }): JsonLdNode;
154
+ creativeWork(i: {
155
+ name: string;
156
+ description?: string;
157
+ url?: string;
158
+ image?: string | string[];
159
+ author?: Author;
160
+ dateCreated?: string;
161
+ keywords?: string | string[];
162
+ }): JsonLdNode;
163
+ webPage(i: {
164
+ name: string;
165
+ url: string;
166
+ description?: string;
167
+ breadcrumb?: JsonLdNode;
168
+ }): JsonLdNode;
169
+ /** One `@graph` document from several nodes; each node's own `@context` is dropped in favour of a single top-level one. */
170
+ graph(nodes: JsonLdNode[]): JsonLdGraph;
112
171
  };
113
172
 
114
173
  /** Renders a `<script type="application/ld+json">` with `data`, escaped for safe embedding in HTML. */
115
174
  declare function JsonLd({ data }: {
116
- data: JsonLdNode | JsonLdNode[];
175
+ data: JsonLdNode | JsonLdNode[] | JsonLdGraph;
117
176
  }): react.JSX.Element;
118
177
 
119
178
  type MarkerProps = {
@@ -171,7 +230,8 @@ type JsonSchema = {
171
230
  required?: string[];
172
231
  additionalProperties?: boolean;
173
232
  };
174
- type ActionKind = "handler" | "form" | "click";
233
+ /** "link": a plain `<a href>` an agent can open without JavaScript (see markupActions). */
234
+ type ActionKind = "handler" | "form" | "click" | "link";
175
235
  interface AgentActionDescriptor {
176
236
  name: string;
177
237
  title?: string;
@@ -180,6 +240,10 @@ interface AgentActionDescriptor {
180
240
  destructive: boolean;
181
241
  readOnly: boolean;
182
242
  kind: ActionKind;
243
+ /** Link actions only: the absolute http(s), mailto: or tel: URL. */
244
+ href?: string;
245
+ /** Link actions with an http(s) href only: open it with a GET request. */
246
+ method?: "GET";
183
247
  }
184
248
  type AgentActionResult = {
185
249
  status: "ok";
@@ -254,6 +318,20 @@ declare function AgentForm<I = Record<string, unknown>, R = unknown>(props: {
254
318
  /** Props that describe a form field to agents. */
255
319
  declare function agentParam(description: string): AgentParamProps;
256
320
 
321
+ interface AgentLinkSpec {
322
+ /** 1 to 128 characters of A-Z a-z 0-9 _ - . Unique per page. */
323
+ name: string;
324
+ description: string;
325
+ /** Marks a link with side effects (for example an unsubscribe URL); it is then not readOnly. */
326
+ destructive?: boolean;
327
+ }
328
+ /**
329
+ * Props that make a plain `<a href>` a link action: listed in agents.json with its absolute href,
330
+ * no JavaScript needed. Only http(s), mailto: and tel: hrefs are listed. Registers nothing and is
331
+ * not exposed through WebMCP; safe to call during SSR and prerendering.
332
+ */
333
+ declare function agentLinkProps(spec: AgentLinkSpec): AgentHintProps;
334
+
257
335
  /** The actions currently exposed on the page, sorted by name. */
258
336
  declare function listAgentActions(): AgentActionDescriptor[];
259
337
  /** Runs an exposed action with `input`. Never throws; failures are reported in the result. */
@@ -266,5 +344,5 @@ declare function executeAgentAction(name: string, input: unknown, signal?: Abort
266
344
  */
267
345
  declare function refreshAgentActions(opts?: RefreshAgentActionsOptions): void;
268
346
 
269
- export { AgentForm, CONTENT_ATTR, ContentRegion, EXCLUDE_ATTR, ExcludeFromContent, JsonLd, PRERENDERED_ATTR, READY_GLOBAL, Seo, SeoProvider, agentParam, executeAgentAction, holdPrerender, hydrateApp, listAgentActions, refreshAgentActions, schema, serializeJsonLd, useAgentAction, useSeo };
270
- export type { AgentActionDescriptor, AgentActionResult, AgentActionSpec, AgentHintProps, AgentParamProps, HeadTag, JsonLdNode, JsonSchema, RefreshAgentActionsOptions, SeoDefaults, SeoProps, SeoProviderProps };
347
+ export { AgentForm, CONTENT_ATTR, ContentRegion, EXCLUDE_ATTR, ExcludeFromContent, JsonLd, PRERENDERED_ATTR, READY_GLOBAL, Seo, SeoProvider, agentLinkProps, agentParam, executeAgentAction, holdPrerender, hydrateApp, listAgentActions, refreshAgentActions, schema, serializeJsonLd, useAgentAction, useSeo };
348
+ export type { AgentActionDescriptor, AgentActionResult, AgentActionSpec, AgentHintProps, AgentLinkSpec, AgentParamProps, HeadTag, JsonLdGraph, JsonLdNode, JsonSchema, RefreshAgentActionsOptions, SeoDefaults, SeoProps, SeoProviderProps };