@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/browser.js +16 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli/index.js +68 -64
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +116 -1
- package/dist/index.js +138 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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 =
|
|
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 (
|
|
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
|