@sightmap/sightmap 0.5.2 → 0.7.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/index.d.ts CHANGED
@@ -51,4 +51,119 @@ interface CanonicalizeOptions {
51
51
  }
52
52
  declare function canonicalize(input: string, opts: CanonicalizeOptions): CanonicalizeResult;
53
53
 
54
- export { type CanonicalizeOptions, type CanonicalizeResult, Diagnostic, type FormatInput, Sightmap, ValidateResult, canonicalize, format, lint, loadDirectory, validate };
54
+ interface BoundingRect {
55
+ x: number;
56
+ y: number;
57
+ width: number;
58
+ height: number;
59
+ }
60
+ /**
61
+ * What an engine adapter's in-page evaluate returns for each sightmap
62
+ * component. matchCount is the number of DOM elements matched (after
63
+ * dedup); samplePosition is the bounding rect of the first match.
64
+ *
65
+ * Engine adapters produce this shape from their own browser-evaluate
66
+ * mechanism; the kernel does not care how.
67
+ */
68
+ interface InPageSightmapMatch {
69
+ name: string;
70
+ selector: string[];
71
+ matchCount: number;
72
+ samplePosition?: BoundingRect | undefined;
73
+ }
74
+ interface EnrichSnapshotInput {
75
+ sightmap: Sightmap;
76
+ currentUrl: string;
77
+ inPageMatches: InPageSightmapMatch[];
78
+ }
79
+ interface SightmapSnapshotComponent {
80
+ name: string;
81
+ selector: string[];
82
+ memory: string[];
83
+ scope: "global" | "view-scoped";
84
+ matchCount: number;
85
+ samplePosition?: BoundingRect | undefined;
86
+ }
87
+ interface EnrichedSnapshot {
88
+ view: {
89
+ name: string;
90
+ route: string;
91
+ memory: string[];
92
+ } | null;
93
+ components: SightmapSnapshotComponent[];
94
+ memory: string[];
95
+ }
96
+ /**
97
+ * Pure function: combines a sightmap, the agent's current URL, and the
98
+ * in-page sightmap match results into an engine-agnostic enriched snapshot.
99
+ *
100
+ * Engine adapters typically wrap this and add their own passthrough fields
101
+ * (e.g. @sightmap/mcp adds ariaSnapshot for the Playwright MCP a11y text).
102
+ * The kernel never touches engine-specific text formats.
103
+ */
104
+ declare function enrichSnapshot(input: EnrichSnapshotInput): EnrichedSnapshot;
105
+
106
+ interface ResolveSightmapActInput {
107
+ componentName: string;
108
+ }
109
+ type ResolveSightmapActResult = {
110
+ kind: "ok";
111
+ componentName: string;
112
+ selector: string;
113
+ allSelectors: string[];
114
+ } | {
115
+ kind: "error";
116
+ message: string;
117
+ };
118
+ /**
119
+ * Resolve a sightmap component by name to a CSS selector that an engine
120
+ * adapter's action verb (click/type/hover/...) can use as the target.
121
+ *
122
+ * Lookup order:
123
+ * 1. Global components (sightmap.globalComponents)
124
+ * 2. View-scoped components, walking each view's components list
125
+ *
126
+ * The first selector in the matching component's selector array is
127
+ * returned; additional selectors are exposed in allSelectors so callers
128
+ * can fall back if the engine's first-selector resolution fails.
129
+ */
130
+ declare function resolveSightmapAct(sightmap: Sightmap, input: ResolveSightmapActInput): ResolveSightmapActResult;
131
+
132
+ interface ParsedNetworkRequest {
133
+ method: string;
134
+ url: string;
135
+ status: number;
136
+ statusText: string;
137
+ }
138
+ interface AnnotatedNetworkRequest extends ParsedNetworkRequest {
139
+ sightmapName?: string;
140
+ sightmapMemory?: string[];
141
+ }
142
+ /**
143
+ * Cross-reference each captured network request with sightmap's `requests:`
144
+ * declarations. When a match is found, attaches the sightmap name + memory
145
+ * to the response. Unmatched requests are returned unchanged.
146
+ *
147
+ * The `requests` input is already normalized; this function does not parse
148
+ * any engine-specific text format. Engine adapters do their own parsing.
149
+ */
150
+ declare function annotateNetworkRequests(sightmap: Sightmap, requests: ParsedNetworkRequest[]): AnnotatedNetworkRequest[];
151
+
152
+ /**
153
+ * Build the in-page evaluate function body. Embeds the component selectors
154
+ * inline; the page only needs to query the DOM. No sightmap-js dependency
155
+ * in the page context.
156
+ *
157
+ * Engine adapters serialize this string and pass it to their browser's
158
+ * `evaluate` primitive (Playwright MCP's `browser_evaluate`, Playwright
159
+ * CLI's `playwright-cli eval`, etc.). The output shape is
160
+ * `{ url: string, matches: InPageSightmapMatch[] }` — adapters do their
161
+ * own parsing of the engine's wrapping (e.g. Playwright MCP's
162
+ * `### Result` framing).
163
+ */
164
+ declare function buildInPageEvalFunction(components: Array<{
165
+ name: string;
166
+ selector: string[];
167
+ }>): string;
168
+
169
+ export { type AnnotatedNetworkRequest, type BoundingRect, type CanonicalizeOptions, type CanonicalizeResult, Diagnostic, type EnrichSnapshotInput, type EnrichedSnapshot, type FormatInput, type InPageSightmapMatch, type ParsedNetworkRequest, type ResolveSightmapActInput, type ResolveSightmapActResult, Sightmap, type SightmapSnapshotComponent, ValidateResult, annotateNetworkRequests, buildInPageEvalFunction, canonicalize, enrichSnapshot, format, lint, loadDirectory, resolveSightmapAct, validate };
package/dist/index.js CHANGED
@@ -304,6 +304,17 @@ function splitSegments(path) {
304
304
  const trimmed = path.startsWith("/") ? path.slice(1) : path;
305
305
  return trimmed.split("/");
306
306
  }
307
+ function routeSpecificity(route) {
308
+ if (route === "/") return 1;
309
+ let total = 0;
310
+ for (const seg of route.split("/")) {
311
+ if (seg === "" || seg === "**") continue;
312
+ if (seg === "*") total += 1;
313
+ else if (seg.startsWith(":")) total += 2;
314
+ else total += 3;
315
+ }
316
+ return total;
317
+ }
307
318
  function matchSegs(pattern, url) {
308
319
  if (pattern.length === 0 && url.length === 0) return true;
309
320
  if (pattern.length === 0) return false;
@@ -326,10 +337,13 @@ function matchSegs(pattern, url) {
326
337
  // src/resolver.ts
327
338
  function resolveByUrl(sightmap, url, method) {
328
339
  let matchedView = null;
340
+ let bestScore = -1;
329
341
  for (const v of sightmap.views) {
330
- if (routeMatch(v.route, url)) {
342
+ if (!routeMatch(v.route, url)) continue;
343
+ const score = routeSpecificity(v.route);
344
+ if (score > bestScore) {
345
+ bestScore = score;
331
346
  matchedView = v;
332
- break;
333
347
  }
334
348
  }
335
349
  const components = [];
@@ -537,12 +551,12 @@ function routeShadowing(sightmap) {
537
551
  for (let j = 1; j < views.length; j++) {
538
552
  const later = views[j];
539
553
  const laterKey = matchSetKey(later.route);
540
- const laterScore = specificity(later.route);
554
+ const laterScore = routeSpecificity(later.route);
541
555
  for (let i = 0; i < j; i++) {
542
556
  const earlier = views[i];
543
557
  if (earlier.route === later.route) continue;
544
558
  if (matchSetKey(earlier.route) !== laterKey) continue;
545
- if (specificity(earlier.route) < laterScore) continue;
559
+ if (routeSpecificity(earlier.route) < laterScore) continue;
546
560
  out.push({
547
561
  severity: "warning",
548
562
  code: ROUTE_SHADOWING,
@@ -556,16 +570,6 @@ function routeShadowing(sightmap) {
556
570
  function matchSetKey(route) {
557
571
  return route.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
558
572
  }
559
- function specificity(route) {
560
- let total = 0;
561
- for (const seg of route.split("/")) {
562
- if (seg === "" || seg === "**") continue;
563
- if (seg === "*") total += 1;
564
- else if (seg.startsWith(":")) total += 2;
565
- else total += 3;
566
- }
567
- return total;
568
- }
569
573
 
570
574
  // src/lintRules/unknownSource.ts
571
575
  import { stat } from "fs/promises";
@@ -968,6 +972,122 @@ function serialize(doc) {
968
972
  });
969
973
  return out.endsWith("\n") ? out : out + "\n";
970
974
  }
975
+
976
+ // src/enrich/snapshot.ts
977
+ function enrichSnapshot(input) {
978
+ const { sightmap, currentUrl, inPageMatches } = input;
979
+ const result = match(sightmap, { url: currentUrl });
980
+ const inPageByName = /* @__PURE__ */ new Map();
981
+ for (const m of inPageMatches) inPageByName.set(m.name, m);
982
+ const components = result.components.map((c) => {
983
+ const inPage = inPageByName.get(c.name);
984
+ return {
985
+ name: c.name,
986
+ selector: c.selector,
987
+ memory: c.memory ?? [],
988
+ scope: c.scope,
989
+ matchCount: inPage?.matchCount ?? 0,
990
+ ...inPage?.samplePosition !== void 0 ? { samplePosition: inPage.samplePosition } : {}
991
+ };
992
+ });
993
+ return {
994
+ view: result.view ? {
995
+ name: result.view.name,
996
+ route: result.view.route,
997
+ memory: result.view.memory ?? []
998
+ } : null,
999
+ components,
1000
+ memory: result.memory ?? []
1001
+ };
1002
+ }
1003
+
1004
+ // src/enrich/act.ts
1005
+ function resolveSightmapAct(sightmap, input) {
1006
+ const found = findComponentByName(sightmap, input.componentName);
1007
+ if (found === null) {
1008
+ return {
1009
+ kind: "error",
1010
+ message: `Component "${input.componentName}" not found in the loaded sightmap.`
1011
+ };
1012
+ }
1013
+ const selectors = Array.isArray(found.selector) ? found.selector : [];
1014
+ if (selectors.length === 0) {
1015
+ return {
1016
+ kind: "error",
1017
+ message: `Component "${input.componentName}" has no selector \u2014 cannot dispatch an action.`
1018
+ };
1019
+ }
1020
+ return {
1021
+ kind: "ok",
1022
+ componentName: input.componentName,
1023
+ selector: selectors[0],
1024
+ allSelectors: selectors
1025
+ };
1026
+ }
1027
+ function findComponentByName(sightmap, name) {
1028
+ for (const c of sightmap.globalComponents) {
1029
+ if (c.name === name) return c;
1030
+ }
1031
+ for (const v of sightmap.views) {
1032
+ for (const c of v.components ?? []) {
1033
+ if (c.name === name) return c;
1034
+ }
1035
+ }
1036
+ return null;
1037
+ }
1038
+
1039
+ // src/enrich/network.ts
1040
+ function annotateNetworkRequests(sightmap, requests) {
1041
+ return requests.map((req) => {
1042
+ const result = match(sightmap, { url: req.url, method: req.method });
1043
+ const first = result.requests[0];
1044
+ if (first === void 0) return req;
1045
+ const annotated = { ...req, sightmapName: first.name };
1046
+ if (first.memory && first.memory.length > 0) {
1047
+ annotated.sightmapMemory = first.memory;
1048
+ }
1049
+ return annotated;
1050
+ });
1051
+ }
1052
+
1053
+ // src/enrich/in-page.ts
1054
+ function buildInPageEvalFunction(components) {
1055
+ const componentsJson = JSON.stringify(components);
1056
+ return `() => {
1057
+ const components = ${componentsJson};
1058
+ const matches = components.map((c) => {
1059
+ const selectors = c.selector || [];
1060
+ const seen = new Set();
1061
+ const elements = [];
1062
+ for (const sel of selectors) {
1063
+ try {
1064
+ const found = document.querySelectorAll(sel);
1065
+ for (const el of Array.from(found)) {
1066
+ if (!seen.has(el)) {
1067
+ seen.add(el);
1068
+ elements.push(el);
1069
+ }
1070
+ }
1071
+ } catch {
1072
+ // Invalid selector \u2014 skip silently.
1073
+ }
1074
+ }
1075
+ const first = elements[0];
1076
+ let samplePosition;
1077
+ if (first && typeof first.getBoundingClientRect === "function") {
1078
+ const r = first.getBoundingClientRect();
1079
+ samplePosition = { x: r.x, y: r.y, width: r.width, height: r.height };
1080
+ }
1081
+ return {
1082
+ name: c.name,
1083
+ selector: selectors,
1084
+ matchCount: elements.length,
1085
+ samplePosition,
1086
+ };
1087
+ });
1088
+ return { url: window.location.href, matches };
1089
+ }`;
1090
+ }
971
1091
  export {
972
1092
  DUPLICATE_ROUTE,
973
1093
  DUPLICATE_VIEW_NAME,
@@ -982,7 +1102,10 @@ export {
982
1102
  SELECTOR_SYNTAX,
983
1103
  UNKNOWN_SOURCE,
984
1104
  UNKNOWN_VERSION,
1105
+ annotateNetworkRequests,
1106
+ buildInPageEvalFunction,
985
1107
  canonicalize,
1108
+ enrichSnapshot,
986
1109
  explain,
987
1110
  format,
988
1111
  lint,
@@ -990,6 +1113,7 @@ export {
990
1113
  match,
991
1114
  merge,
992
1115
  parse,
1116
+ resolveSightmapAct,
993
1117
  validate
994
1118
  };
995
1119
  //# sourceMappingURL=index.js.map