@sightmap/mcp 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.
- package/README.md +79 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +1361 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +1206 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1361 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/loadMerged.ts
|
|
13
|
+
var loadMerged_exports = {};
|
|
14
|
+
__export(loadMerged_exports, {
|
|
15
|
+
mergeSightmaps: () => mergeSightmaps
|
|
16
|
+
});
|
|
17
|
+
import { loadDirectory } from "@sightmap/sightmap";
|
|
18
|
+
async function mergeSightmaps(dirs) {
|
|
19
|
+
if (dirs.length === 0) {
|
|
20
|
+
return {
|
|
21
|
+
version: 1,
|
|
22
|
+
views: [],
|
|
23
|
+
globalComponents: [],
|
|
24
|
+
globalRequests: [],
|
|
25
|
+
fileMemory: [],
|
|
26
|
+
diagnostics: [],
|
|
27
|
+
__brand: "Sightmap"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const loaded = await Promise.all(dirs.map((d) => loadDirectory(d)));
|
|
31
|
+
if (loaded.length === 1) return loaded[0];
|
|
32
|
+
const head = loaded[0];
|
|
33
|
+
return {
|
|
34
|
+
...head,
|
|
35
|
+
views: loaded.flatMap((s) => s.views),
|
|
36
|
+
globalComponents: loaded.flatMap((s) => s.globalComponents),
|
|
37
|
+
globalRequests: loaded.flatMap((s) => s.globalRequests),
|
|
38
|
+
fileMemory: loaded.flatMap((s) => s.fileMemory),
|
|
39
|
+
diagnostics: loaded.flatMap((s) => s.diagnostics)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var init_loadMerged = __esm({
|
|
43
|
+
"src/loadMerged.ts"() {
|
|
44
|
+
"use strict";
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// src/cli.ts
|
|
49
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
50
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
51
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
52
|
+
import { resolve } from "path";
|
|
53
|
+
|
|
54
|
+
// src/server.ts
|
|
55
|
+
import { match as sightmapMatch4 } from "@sightmap/sightmap";
|
|
56
|
+
|
|
57
|
+
// src/tools/match.ts
|
|
58
|
+
import { match as sightmapMatch } from "@sightmap/sightmap";
|
|
59
|
+
function handleSightmapMatch(sightmap, input) {
|
|
60
|
+
const result = sightmapMatch(sightmap, {
|
|
61
|
+
url: input.url,
|
|
62
|
+
...input.method !== void 0 ? { method: input.method } : {}
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
view: result.view ? {
|
|
66
|
+
name: result.view.name,
|
|
67
|
+
route: result.view.route,
|
|
68
|
+
...result.view.description !== void 0 ? { description: result.view.description } : {}
|
|
69
|
+
} : null,
|
|
70
|
+
components: result.components.map((c) => ({
|
|
71
|
+
name: c.name,
|
|
72
|
+
selector: c.selector,
|
|
73
|
+
memory: c.memory ?? [],
|
|
74
|
+
scope: c.scope
|
|
75
|
+
})),
|
|
76
|
+
requests: result.requests.map((r) => ({
|
|
77
|
+
name: r.name,
|
|
78
|
+
route: r.route,
|
|
79
|
+
...r.method !== void 0 ? { method: r.method } : {},
|
|
80
|
+
memory: r.memory ?? []
|
|
81
|
+
})),
|
|
82
|
+
memory: result.memory ?? [],
|
|
83
|
+
viewMemory: result.view?.memory ?? []
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/tools/snapshot.ts
|
|
88
|
+
import { match as sightmapMatch2 } from "@sightmap/sightmap";
|
|
89
|
+
function buildSightmapSnapshotResponse(opts) {
|
|
90
|
+
const { sightmap, currentUrl, ariaSnapshotText, inPageMatches } = opts;
|
|
91
|
+
const result = sightmapMatch2(sightmap, { url: currentUrl });
|
|
92
|
+
const inPageByName = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const m of inPageMatches) inPageByName.set(m.name, m);
|
|
94
|
+
const components = result.components.map((c) => {
|
|
95
|
+
const inPage = inPageByName.get(c.name);
|
|
96
|
+
return {
|
|
97
|
+
name: c.name,
|
|
98
|
+
selector: c.selector,
|
|
99
|
+
memory: c.memory ?? [],
|
|
100
|
+
scope: c.scope,
|
|
101
|
+
matchCount: inPage?.matchCount ?? 0,
|
|
102
|
+
...inPage?.samplePosition !== void 0 ? { samplePosition: inPage.samplePosition } : {}
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
view: result.view ? {
|
|
107
|
+
name: result.view.name,
|
|
108
|
+
route: result.view.route,
|
|
109
|
+
memory: result.view.memory ?? []
|
|
110
|
+
} : null,
|
|
111
|
+
components,
|
|
112
|
+
memory: result.memory ?? [],
|
|
113
|
+
ariaSnapshot: ariaSnapshotText
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/tools/act.ts
|
|
118
|
+
function resolveSightmapAct(sightmap, input) {
|
|
119
|
+
const found = findComponentByName(sightmap, input.componentName);
|
|
120
|
+
if (found === null) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "error",
|
|
123
|
+
message: `Component "${input.componentName}" not found in the loaded sightmap.`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const selectors = Array.isArray(found.selector) ? found.selector : [];
|
|
127
|
+
if (selectors.length === 0) {
|
|
128
|
+
return {
|
|
129
|
+
kind: "error",
|
|
130
|
+
message: `Component "${input.componentName}" has no selector \u2014 cannot dispatch an action.`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
kind: "ok",
|
|
135
|
+
componentName: input.componentName,
|
|
136
|
+
selector: selectors[0],
|
|
137
|
+
allSelectors: selectors
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function findComponentByName(sightmap, name) {
|
|
141
|
+
for (const c of sightmap.globalComponents) {
|
|
142
|
+
if (c.name === name) return c;
|
|
143
|
+
}
|
|
144
|
+
for (const v of sightmap.views) {
|
|
145
|
+
for (const c of v.components ?? []) {
|
|
146
|
+
if (c.name === name) return c;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/tools/network.ts
|
|
153
|
+
import { match as sightmapMatch3 } from "@sightmap/sightmap";
|
|
154
|
+
function parseNetworkRequestsText(text) {
|
|
155
|
+
const lines = text.split(/\r?\n/);
|
|
156
|
+
const requests = [];
|
|
157
|
+
const re = /^\s*\d+\.\s*\[([A-Z]+)\]\s+(\S+)\s+=>\s+\[(\d+)\]\s+(.*?)\s*$/;
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
const m = re.exec(line);
|
|
160
|
+
if (m === null) continue;
|
|
161
|
+
requests.push({
|
|
162
|
+
method: m[1] ?? "",
|
|
163
|
+
url: m[2] ?? "",
|
|
164
|
+
status: Number.parseInt(m[3] ?? "0", 10),
|
|
165
|
+
statusText: m[4] ?? ""
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return requests;
|
|
169
|
+
}
|
|
170
|
+
function annotateNetworkRequests(sightmap, requests) {
|
|
171
|
+
return requests.map((req) => {
|
|
172
|
+
const result = sightmapMatch3(sightmap, { url: req.url, method: req.method });
|
|
173
|
+
const first = result.requests[0];
|
|
174
|
+
if (first === void 0) return req;
|
|
175
|
+
const annotated = { ...req, sightmapName: first.name };
|
|
176
|
+
if (first.memory && first.memory.length > 0) {
|
|
177
|
+
annotated.sightmapMemory = first.memory;
|
|
178
|
+
}
|
|
179
|
+
return annotated;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/tools/curate/list.ts
|
|
184
|
+
function handleListViews(sightmap, input) {
|
|
185
|
+
const detail = input.detail ?? "summary";
|
|
186
|
+
const sorted = [...sightmap.views].sort(
|
|
187
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
188
|
+
);
|
|
189
|
+
if (detail === "full") {
|
|
190
|
+
return { views: sorted };
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
views: sorted.map((v) => {
|
|
194
|
+
const summary = { name: v.name, route: v.route };
|
|
195
|
+
if (v.description !== void 0) summary.description = v.description;
|
|
196
|
+
return summary;
|
|
197
|
+
})
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/tools/curate/get.ts
|
|
202
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
203
|
+
import { parse as parse2 } from "@sightmap/sightmap";
|
|
204
|
+
|
|
205
|
+
// src/curate/locator.ts
|
|
206
|
+
import { readdir, readFile } from "fs/promises";
|
|
207
|
+
import { join } from "path";
|
|
208
|
+
import { parse } from "@sightmap/sightmap";
|
|
209
|
+
async function findFileForView(sightmapDir, viewName, options = {}) {
|
|
210
|
+
const entries = await readdir(sightmapDir);
|
|
211
|
+
const yamlFiles = entries.filter((e) => e.endsWith(".yaml") || e.endsWith(".yml")).sort();
|
|
212
|
+
let firstMatch = null;
|
|
213
|
+
let matchCount = 0;
|
|
214
|
+
for (const file of yamlFiles) {
|
|
215
|
+
const path = join(sightmapDir, file);
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
const text = await readFile(path, "utf8");
|
|
219
|
+
parsed = parse(text, { sourceFile: path });
|
|
220
|
+
} catch {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (parsed.views?.some((v) => v.name === viewName)) {
|
|
224
|
+
matchCount++;
|
|
225
|
+
if (firstMatch === null) firstMatch = path;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (matchCount > 1 && options.onWarning) {
|
|
229
|
+
options.onWarning(
|
|
230
|
+
`duplicate view name "${viewName}" found in ${matchCount} files; using ${firstMatch}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return firstMatch;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/tools/curate/get.ts
|
|
237
|
+
async function handleGetView(input) {
|
|
238
|
+
const file = await findFileForView(input.sightmapDir, input.name);
|
|
239
|
+
if (file === null) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`View "${input.name}" not found in ${input.sightmapDir}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const text = await readFile2(file, "utf8");
|
|
245
|
+
const parsed = parse2(text, { sourceFile: file });
|
|
246
|
+
const view = parsed.views?.find((v) => v.name === input.name);
|
|
247
|
+
if (view === void 0) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`View "${input.name}" not found in ${file} (locator drift?)`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return { view, file };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/tools/curate/check.ts
|
|
256
|
+
import { lint } from "@sightmap/sightmap";
|
|
257
|
+
async function handleCheck(sightmap, input) {
|
|
258
|
+
const level = input.level ?? "quality";
|
|
259
|
+
const schemaDiag = [...sightmap.diagnostics];
|
|
260
|
+
if (level === "schema") return { diagnostics: schemaDiag };
|
|
261
|
+
const lintDiag = await lint(sightmap);
|
|
262
|
+
return { diagnostics: [...schemaDiag, ...lintDiag] };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/tools/curate/update.ts
|
|
266
|
+
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
267
|
+
import {
|
|
268
|
+
parse as parse3,
|
|
269
|
+
format
|
|
270
|
+
} from "@sightmap/sightmap";
|
|
271
|
+
async function handleUpdateView(input) {
|
|
272
|
+
if (input.patch.memory_append && input.patch.memory_replace) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"cannot use both memory_append and memory_replace in the same patch"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const file = await findFileForView(input.sightmapDir, input.name);
|
|
278
|
+
if (file === null) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`View "${input.name}" not found in ${input.sightmapDir}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const text = await readFile3(file, "utf8");
|
|
284
|
+
const parsed = parse3(text, { sourceFile: file });
|
|
285
|
+
const views = parsed.views ?? [];
|
|
286
|
+
const idx = views.findIndex((v) => v.name === input.name);
|
|
287
|
+
if (idx < 0) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`View "${input.name}" not found in ${file} (locator drift?)`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const before = views[idx];
|
|
293
|
+
const after = { ...before };
|
|
294
|
+
if (input.patch.description !== void 0) {
|
|
295
|
+
after.description = input.patch.description;
|
|
296
|
+
}
|
|
297
|
+
if (input.patch.intent !== void 0) {
|
|
298
|
+
after.intent = input.patch.intent;
|
|
299
|
+
}
|
|
300
|
+
if (input.patch.memory_append) {
|
|
301
|
+
after.memory = [...before.memory ?? [], ...input.patch.memory_append];
|
|
302
|
+
}
|
|
303
|
+
if (input.patch.memory_replace) {
|
|
304
|
+
after.memory = [...input.patch.memory_replace];
|
|
305
|
+
}
|
|
306
|
+
const nextViews = [...views];
|
|
307
|
+
nextViews[idx] = after;
|
|
308
|
+
const nextDoc = stripFragmentBrand(parsed);
|
|
309
|
+
nextDoc.views = nextViews;
|
|
310
|
+
const out = format(nextDoc);
|
|
311
|
+
await writeFile(file, out, "utf8");
|
|
312
|
+
return { ok: true, file, written: after };
|
|
313
|
+
}
|
|
314
|
+
function stripFragmentBrand(input) {
|
|
315
|
+
const out = {};
|
|
316
|
+
for (const k of Object.keys(input)) {
|
|
317
|
+
if (k === "__brand" || k === "__sourceFile") continue;
|
|
318
|
+
out[k] = input[k];
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/tools/curate/init.ts
|
|
324
|
+
import { mkdir, writeFile as writeFile2, access } from "fs/promises";
|
|
325
|
+
import { join as join2 } from "path";
|
|
326
|
+
|
|
327
|
+
// src/curate/scaffolder.ts
|
|
328
|
+
import { format as format2 } from "@sightmap/sightmap";
|
|
329
|
+
function makeStarterSightmap() {
|
|
330
|
+
return format2({ version: 1 });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/tools/curate/init.ts
|
|
334
|
+
async function handleInitProject(input) {
|
|
335
|
+
const root = input.dir ?? process.cwd();
|
|
336
|
+
const sightmapDir = join2(root, ".sightmap");
|
|
337
|
+
const appFile = join2(sightmapDir, "app.yaml");
|
|
338
|
+
if (input.force !== true) {
|
|
339
|
+
let exists = false;
|
|
340
|
+
try {
|
|
341
|
+
await access(sightmapDir);
|
|
342
|
+
exists = true;
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
if (exists) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`.sightmap/ already exists at ${sightmapDir} \u2014 pass force=true to overwrite`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
await mkdir(sightmapDir, { recursive: true });
|
|
352
|
+
await writeFile2(appFile, makeStarterSightmap(), "utf8");
|
|
353
|
+
return { ok: true, files: [appFile] };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/prompts/sepTrack.ts
|
|
357
|
+
var VALID_PHASES = ["draft", "spec", "fixture", "verify"];
|
|
358
|
+
function buildDraftPhase(args) {
|
|
359
|
+
const slug = args.slug ?? "{slug}";
|
|
360
|
+
return [
|
|
361
|
+
`# SEP track \u2014 phase 1 of 4: Draft the SEP`,
|
|
362
|
+
``,
|
|
363
|
+
`You are helping the user author a Sightmap Enhancement Proposal (SEP).`,
|
|
364
|
+
`An SEP is required for any change to the spec's schema, semantics, or`,
|
|
365
|
+
`discovery rules. See \`seps/README.md\` for the full list.`,
|
|
366
|
+
``,
|
|
367
|
+
`## Steps for this phase`,
|
|
368
|
+
``,
|
|
369
|
+
`1. **Pick a number.** Look at \`seps/\` and find the lowest unused 4-digit`,
|
|
370
|
+
` integer (skip \`0000-template.md\`). Call this NNNN.`,
|
|
371
|
+
`2. **Copy the template.** \`cp seps/0000-template.md seps/NNNN-${slug}.md\`.`,
|
|
372
|
+
`3. **Fill in the front-matter.** Title, author, today's date, status: Draft.`,
|
|
373
|
+
`4. **Write the body.** Sections from the template:`,
|
|
374
|
+
` - Summary (one paragraph; treat as the abstract)`,
|
|
375
|
+
` - Motivation (concrete user-facing problem; name a real app or pattern)`,
|
|
376
|
+
` - Proposal (Shape, Semantics, Conformance \u2014 show YAML, show JSON Schema diff)`,
|
|
377
|
+
` - Alternatives considered (at least two, including "do nothing")`,
|
|
378
|
+
` - Migration (what changes for existing users)`,
|
|
379
|
+
` - Open questions`,
|
|
380
|
+
`5. **Read it back.** Does it pass the acceptance checklist in \`seps/README.md\`?`,
|
|
381
|
+
``,
|
|
382
|
+
`## STOP HERE`,
|
|
383
|
+
``,
|
|
384
|
+
`Show the draft to the user. Ask for explicit "go" before continuing.`,
|
|
385
|
+
`Do **not** open the spec edit until the user confirms the SEP draft.`,
|
|
386
|
+
``,
|
|
387
|
+
`When the user is ready, call this prompt again with`,
|
|
388
|
+
`\`phase: "spec"\` and pass \`sep_number: "NNNN"\` and \`slug: "${slug}"\`.`
|
|
389
|
+
].join("\n");
|
|
390
|
+
}
|
|
391
|
+
function buildSpecPhase(args) {
|
|
392
|
+
const slug = args.slug ?? "{slug}";
|
|
393
|
+
const sep = args.sep_number ?? "NNNN";
|
|
394
|
+
return [
|
|
395
|
+
`# SEP track \u2014 phase 2 of 4: Propose the spec change`,
|
|
396
|
+
``,
|
|
397
|
+
`Building on \`seps/${sep}-${slug}.md\`.`,
|
|
398
|
+
``,
|
|
399
|
+
`## Steps for this phase`,
|
|
400
|
+
``,
|
|
401
|
+
`1. **Identify the spec files affected.** Most changes touch:`,
|
|
402
|
+
` - \`spec/v1/sightmap.schema.json\` (the JSON Schema)`,
|
|
403
|
+
` - \`spec/v1/sightmap.spec.md\` (the prose spec)`,
|
|
404
|
+
` - Possibly \`spec/v1/examples/\` if the change adds a shape worth showing`,
|
|
405
|
+
`2. **Make the edits.** Keep the diff small. The SEP is the design doc;`,
|
|
406
|
+
` the spec edit is the precise textual change.`,
|
|
407
|
+
`3. **Run \`pnpm -F @sightmap/sightmap test\`.** Existing examples must still`,
|
|
408
|
+
` parse and validate, unless the SEP is explicitly breaking \u2014 in which`,
|
|
409
|
+
` case the SEP's Migration section must say so.`,
|
|
410
|
+
`4. **Cross-reference.** Add a "Spec changes:" line near the top of the SEP`,
|
|
411
|
+
` listing the touched files. Add a comment in the schema file pointing`,
|
|
412
|
+
` back to \`seps/${sep}-${slug}.md\` if the rationale isn't obvious from`,
|
|
413
|
+
` the diff alone.`,
|
|
414
|
+
``,
|
|
415
|
+
`## STOP HERE`,
|
|
416
|
+
``,
|
|
417
|
+
`Show the spec diff to the user. Ask for explicit "go" before continuing.`,
|
|
418
|
+
`Do **not** start the conformance fixture until the user confirms the spec edit.`,
|
|
419
|
+
``,
|
|
420
|
+
`When the user is ready, call this prompt again with`,
|
|
421
|
+
`\`phase: "fixture"\`, \`sep_number: "${sep}"\`, \`slug: "${slug}"\`.`
|
|
422
|
+
].join("\n");
|
|
423
|
+
}
|
|
424
|
+
function buildFixturePhase(args) {
|
|
425
|
+
const slug = args.slug ?? "{slug}";
|
|
426
|
+
const sep = args.sep_number ?? "NNNN";
|
|
427
|
+
const fixtureGuess = sep.startsWith("0") ? sep.slice(1) : sep;
|
|
428
|
+
return [
|
|
429
|
+
`# SEP track \u2014 phase 3 of 4: Write the conformance fixture`,
|
|
430
|
+
``,
|
|
431
|
+
`Building on \`seps/${sep}-${slug}.md\` and the spec edit from phase 2.`,
|
|
432
|
+
``,
|
|
433
|
+
`## Steps for this phase`,
|
|
434
|
+
``,
|
|
435
|
+
`1. **Pick a fixture number.** Conformance fixtures use 3-digit numbers`,
|
|
436
|
+
` (\`001-minimal\`, \`002-multi-file-merge\`, \u2026). Pick the lowest unused`,
|
|
437
|
+
` number \u2014 likely \`${fixtureGuess}\` if the SEP and fixture numberings`,
|
|
438
|
+
` line up, but verify against \`conformance/\`.`,
|
|
439
|
+
`2. **Create the directory.** \`mkdir conformance/${fixtureGuess}-${slug}\`.`,
|
|
440
|
+
`3. **Write the input.** \`conformance/${fixtureGuess}-${slug}/sightmap/\``,
|
|
441
|
+
` contains one or more \`.yaml\` files demonstrating the new behavior.`,
|
|
442
|
+
` Keep it minimal \u2014 one fixture, one behavior under test.`,
|
|
443
|
+
`4. **Write the expected output.** \`conformance/${fixtureGuess}-${slug}/expected.json\``,
|
|
444
|
+
` is the canonical merged + resolved sightmap that any spec-conformant`,
|
|
445
|
+
` parser must produce. Use the existing \`@sightmap/sightmap\` parser`,
|
|
446
|
+
` to generate the baseline, then hand-audit it against the SEP.`,
|
|
447
|
+
`5. **Run the conformance suite.** \`pnpm -F @sightmap/sightmap test\` should`,
|
|
448
|
+
` pick up the new fixture and validate it.`,
|
|
449
|
+
``,
|
|
450
|
+
`## STOP HERE`,
|
|
451
|
+
``,
|
|
452
|
+
`Show the fixture (input + expected.json) to the user. Ask for explicit`,
|
|
453
|
+
`"go" before continuing. Do **not** declare the SEP ready for review until`,
|
|
454
|
+
`the user confirms all three artefacts.`,
|
|
455
|
+
``,
|
|
456
|
+
`When the user is ready, call this prompt again with`,
|
|
457
|
+
`\`phase: "verify"\`, \`sep_number: "${sep}"\`, \`slug: "${slug}"\`.`
|
|
458
|
+
].join("\n");
|
|
459
|
+
}
|
|
460
|
+
function buildVerifyPhase(args) {
|
|
461
|
+
const slug = args.slug ?? "{slug}";
|
|
462
|
+
const sep = args.sep_number ?? "NNNN";
|
|
463
|
+
return [
|
|
464
|
+
`# SEP track \u2014 phase 4 of 4: Verify and hand off`,
|
|
465
|
+
``,
|
|
466
|
+
`## Summary`,
|
|
467
|
+
``,
|
|
468
|
+
`SEP ${sep} \u2190 spec edit \u2190 conformance fixture, ready for review.`,
|
|
469
|
+
``,
|
|
470
|
+
`Three artefacts should now exist on this branch:`,
|
|
471
|
+
``,
|
|
472
|
+
`- \`seps/${sep}-${slug}.md\` (Draft \u2192 flip to Review when filing the PR)`,
|
|
473
|
+
`- spec/ edits (listed in the SEP's "Spec changes:" line)`,
|
|
474
|
+
`- \`conformance/<fixture-number>-${slug}/\` with \`sightmap/\` input and`,
|
|
475
|
+
` \`expected.json\` output`,
|
|
476
|
+
``,
|
|
477
|
+
`## Next steps for the user`,
|
|
478
|
+
``,
|
|
479
|
+
`1. Open a draft PR titled "SEP ${sep}: <short title>".`,
|
|
480
|
+
`2. PR description: link the SEP, the spec diff, and the fixture; link any`,
|
|
481
|
+
` prior Discussion or Issue.`,
|
|
482
|
+
`3. Flip the SEP front-matter \`status\` from \`Draft\` to \`Review\` when`,
|
|
483
|
+
` ready for substantive feedback.`,
|
|
484
|
+
`4. Per \`seps/README.md\`: minimum review window is 7 days for minor`,
|
|
485
|
+
` changes, 14 days for anything affecting the JSON Schema.`,
|
|
486
|
+
``,
|
|
487
|
+
`Track is complete. No further phases.`
|
|
488
|
+
].join("\n");
|
|
489
|
+
}
|
|
490
|
+
function buildPhase(args) {
|
|
491
|
+
if (typeof args["phase"] !== "string" || args["phase"].length === 0) {
|
|
492
|
+
throw new Error(`sightmap_sep_track: argument "phase" is required`);
|
|
493
|
+
}
|
|
494
|
+
if (!VALID_PHASES.includes(args["phase"])) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`sightmap_sep_track: unknown phase "${args["phase"]}" \u2014 expected one of ${VALID_PHASES.join(", ")}`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
const phase = args["phase"];
|
|
500
|
+
const phaseArgs = {};
|
|
501
|
+
if (typeof args["slug"] === "string") phaseArgs.slug = args["slug"];
|
|
502
|
+
if (typeof args["sep_number"] === "string") phaseArgs.sep_number = args["sep_number"];
|
|
503
|
+
switch (phase) {
|
|
504
|
+
case "draft":
|
|
505
|
+
return buildDraftPhase(phaseArgs);
|
|
506
|
+
case "spec":
|
|
507
|
+
return buildSpecPhase(phaseArgs);
|
|
508
|
+
case "fixture":
|
|
509
|
+
return buildFixturePhase(phaseArgs);
|
|
510
|
+
case "verify":
|
|
511
|
+
return buildVerifyPhase(phaseArgs);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
var sepTrackPrompt = {
|
|
515
|
+
definition: {
|
|
516
|
+
name: "sightmap_sep_track",
|
|
517
|
+
description: "Walks an author through writing a Sightmap Enhancement Proposal (SEP), the matching spec/ change, and the matching conformance/ fixture in four phases (draft \u2192 spec \u2192 fixture \u2192 verify). Each non-final phase ends with a confirmation gate \u2014 call this prompt again with the next phase only after the user confirms. Inspired by Archcore's architecture_track pattern.",
|
|
518
|
+
arguments: [
|
|
519
|
+
{
|
|
520
|
+
name: "phase",
|
|
521
|
+
description: 'One of "draft", "spec", "fixture", "verify". Required.',
|
|
522
|
+
required: true
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "slug",
|
|
526
|
+
description: "Short kebab-case slug for the SEP (e.g. 'extend-memory').",
|
|
527
|
+
required: false
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "sep_number",
|
|
531
|
+
description: 'Four-digit SEP number assigned in phase "draft" (e.g. "0001").',
|
|
532
|
+
required: false
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
handler: (args) => {
|
|
537
|
+
const text = buildPhase(args);
|
|
538
|
+
return {
|
|
539
|
+
messages: [
|
|
540
|
+
{
|
|
541
|
+
role: "user",
|
|
542
|
+
content: { type: "text", text }
|
|
543
|
+
}
|
|
544
|
+
]
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/prompts/index.ts
|
|
550
|
+
var PROMPT_REGISTRY = [sepTrackPrompt];
|
|
551
|
+
|
|
552
|
+
// src/server.ts
|
|
553
|
+
var SIGHTMAP_MATCH = {
|
|
554
|
+
name: "sightmap_match",
|
|
555
|
+
description: "Pure-query lookup against the loaded sightmap. Returns the matched view, applicable components, applicable requests, and aggregated memory for a given URL. Useful for agent planning before navigation. Does not touch the browser.",
|
|
556
|
+
inputSchema: {
|
|
557
|
+
type: "object",
|
|
558
|
+
properties: {
|
|
559
|
+
url: {
|
|
560
|
+
type: "string",
|
|
561
|
+
description: "The URL to resolve against the sightmap's view routes."
|
|
562
|
+
},
|
|
563
|
+
method: {
|
|
564
|
+
type: "string",
|
|
565
|
+
description: "Optional HTTP method to filter matched requests (e.g., 'GET', 'POST')."
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
required: ["url"],
|
|
569
|
+
additionalProperties: false
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var SIGHTMAP_SNAPSHOT = {
|
|
573
|
+
name: "sightmap_snapshot",
|
|
574
|
+
description: "Returns the upstream ARIA snapshot enriched with sightmap awareness: matched view name, applicable component names with their selectors and live matchCount on the page, view-level memory, and page-aggregated memory. Use this instead of browser_snapshot when you want sightmap-aware semantics in one round-trip.",
|
|
575
|
+
inputSchema: {
|
|
576
|
+
type: "object",
|
|
577
|
+
properties: {},
|
|
578
|
+
additionalProperties: false
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var SIGHTMAP_NETWORK_REQUESTS = {
|
|
582
|
+
name: "sightmap_network_requests",
|
|
583
|
+
description: "Returns recent network requests captured by the upstream, annotated with sightmap names and memory where the request URL+method matches a sightmap `requests:` entry. Use this in place of browser_network_requests when you want sightmap-aware semantics for network calls.",
|
|
584
|
+
inputSchema: {
|
|
585
|
+
type: "object",
|
|
586
|
+
properties: {
|
|
587
|
+
static: {
|
|
588
|
+
type: "boolean",
|
|
589
|
+
description: "Include successful static resources (images, fonts, scripts). Default false."
|
|
590
|
+
},
|
|
591
|
+
filter: {
|
|
592
|
+
type: "string",
|
|
593
|
+
description: "Only return requests whose URL matches this regexp."
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
additionalProperties: false
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
var SIGHTMAP_INIT_PROJECT = {
|
|
600
|
+
name: "sightmap_init_project",
|
|
601
|
+
description: "Scaffold an empty .sightmap/ directory in the target project. Pass `dir` to override the location (default: server cwd). Refuses to overwrite an existing .sightmap/ unless `force=true`.",
|
|
602
|
+
inputSchema: {
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {
|
|
605
|
+
dir: { type: "string", description: "Project root. Default: server cwd." },
|
|
606
|
+
force: { type: "boolean", description: "Default false." }
|
|
607
|
+
},
|
|
608
|
+
additionalProperties: false
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
var SIGHTMAP_UPDATE_VIEW = {
|
|
612
|
+
name: "sightmap_update_view",
|
|
613
|
+
description: "Apply a partial patch to a view's agent-authored fields (description, intent, memory). Memory edits use either `memory_append` (default operation) or `memory_replace` \u2014 pass exactly one. Component / route / selector edits are NOT supported here \u2014 those are codegen-owned; edit source and re-run sightmap-react gen instead.",
|
|
614
|
+
inputSchema: {
|
|
615
|
+
type: "object",
|
|
616
|
+
properties: {
|
|
617
|
+
name: { type: "string" },
|
|
618
|
+
patch: {
|
|
619
|
+
type: "object",
|
|
620
|
+
properties: {
|
|
621
|
+
description: { type: "string" },
|
|
622
|
+
intent: { type: "string" },
|
|
623
|
+
memory_append: { type: "array", items: { type: "string" } },
|
|
624
|
+
memory_replace: { type: "array", items: { type: "string" } }
|
|
625
|
+
},
|
|
626
|
+
additionalProperties: false
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
required: ["name", "patch"],
|
|
630
|
+
additionalProperties: false
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
var SIGHTMAP_CHECK = {
|
|
634
|
+
name: "sightmap_check",
|
|
635
|
+
description: "Validate the loaded sightmap. level='schema' returns only schema-level diagnostics (parse errors, schema failures, merge collisions). level='quality' (default) also runs lint rules: duplicate routes, route shadowing, selector syntax, unknown source attributions. Returns an array of diagnostics; an empty array means clean.",
|
|
636
|
+
inputSchema: {
|
|
637
|
+
type: "object",
|
|
638
|
+
properties: {
|
|
639
|
+
level: {
|
|
640
|
+
type: "string",
|
|
641
|
+
enum: ["schema", "quality"],
|
|
642
|
+
description: "Default 'quality'."
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
additionalProperties: false
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var SIGHTMAP_GET_VIEW = {
|
|
649
|
+
name: "sightmap_get_view",
|
|
650
|
+
description: "Fetch a single view by name, including all memory, components, and requests. Returns the file path the view was read from for subsequent edits. Use sightmap_list_views first to discover view names.",
|
|
651
|
+
inputSchema: {
|
|
652
|
+
type: "object",
|
|
653
|
+
properties: {
|
|
654
|
+
name: { type: "string", description: "View name (case-sensitive)." }
|
|
655
|
+
},
|
|
656
|
+
required: ["name"],
|
|
657
|
+
additionalProperties: false
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
var SIGHTMAP_LIST_VIEWS = {
|
|
661
|
+
name: "sightmap_list_views",
|
|
662
|
+
description: "List all views in the loaded sightmap. Returns compact summaries (name, route, description) by default; pass detail='full' for the complete view objects including memory and components. Token-efficient: prefer the default for any agent that hasn't yet narrowed to a specific view.",
|
|
663
|
+
inputSchema: {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
detail: {
|
|
667
|
+
type: "string",
|
|
668
|
+
enum: ["summary", "full"],
|
|
669
|
+
description: "Default 'summary'."
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
additionalProperties: false
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
var SIGHTMAP_ACT = {
|
|
676
|
+
name: "sightmap_act",
|
|
677
|
+
description: "Performs an action on a UI element. Prefer `componentName` so the agent operates in sightmap semantics; fall back to a raw `selector` for one-off targets the sightmap doesn't define (e.g., a stray link or a third-party widget). Supported actions: 'click' (browser_click), 'type' (browser_type, requires `text`), 'hover' (browser_hover). Pass exactly one of `componentName` or `selector`.",
|
|
678
|
+
inputSchema: {
|
|
679
|
+
type: "object",
|
|
680
|
+
properties: {
|
|
681
|
+
componentName: {
|
|
682
|
+
type: "string",
|
|
683
|
+
description: "Sightmap component name to act on (case-sensitive). Mutually exclusive with `selector`."
|
|
684
|
+
},
|
|
685
|
+
selector: {
|
|
686
|
+
type: "string",
|
|
687
|
+
description: "Escape hatch: a raw CSS selector for a target the sightmap doesn't name. Same tagging + dispatch path as componentName. Use this only when no sightmap component fits \u2014 most actions should use `componentName` so the agent's reasoning stays in sightmap semantics. Mutually exclusive with `componentName`."
|
|
688
|
+
},
|
|
689
|
+
action: {
|
|
690
|
+
type: "string",
|
|
691
|
+
enum: ["click", "type", "hover"],
|
|
692
|
+
description: "The action to perform on the component."
|
|
693
|
+
},
|
|
694
|
+
text: {
|
|
695
|
+
type: "string",
|
|
696
|
+
description: "Required for action='type'. Text to enter into the matched element."
|
|
697
|
+
},
|
|
698
|
+
submit: {
|
|
699
|
+
type: "boolean",
|
|
700
|
+
description: "For action='type' only. Press Enter after typing."
|
|
701
|
+
},
|
|
702
|
+
instance: {
|
|
703
|
+
type: "integer",
|
|
704
|
+
minimum: 0,
|
|
705
|
+
description: "Zero-based index of the matching element when the component selector resolves to multiple elements (default 0). Use sightmap_snapshot.matchCount to discover how many instances exist. Mutually exclusive with `containingText`."
|
|
706
|
+
},
|
|
707
|
+
containingText: {
|
|
708
|
+
type: "string",
|
|
709
|
+
description: "Disambiguator for multi-match components: pick the first element whose visible text contains this substring (case-insensitive). Use this instead of `instance` when you can identify the element by its visible label rather than position. Mutually exclusive with `instance`."
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
required: ["componentName", "action"],
|
|
713
|
+
additionalProperties: false
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var SHADOWED_UPSTREAM_TOOLS = /* @__PURE__ */ new Set([
|
|
717
|
+
"browser_snapshot",
|
|
718
|
+
"browser_click",
|
|
719
|
+
"browser_type",
|
|
720
|
+
"browser_hover",
|
|
721
|
+
"browser_network_requests"
|
|
722
|
+
]);
|
|
723
|
+
var SightmapMcpServer = class {
|
|
724
|
+
sightmap;
|
|
725
|
+
upstream;
|
|
726
|
+
curateRoot;
|
|
727
|
+
constructor(opts) {
|
|
728
|
+
this.sightmap = opts.sightmap;
|
|
729
|
+
this.upstream = opts.upstream;
|
|
730
|
+
this.curateRoot = opts.curateRoot;
|
|
731
|
+
}
|
|
732
|
+
/** Synchronous list of sightmap-aware tools that don't need an upstream. */
|
|
733
|
+
listTools() {
|
|
734
|
+
return [
|
|
735
|
+
SIGHTMAP_MATCH,
|
|
736
|
+
SIGHTMAP_LIST_VIEWS,
|
|
737
|
+
SIGHTMAP_GET_VIEW,
|
|
738
|
+
SIGHTMAP_CHECK,
|
|
739
|
+
SIGHTMAP_UPDATE_VIEW,
|
|
740
|
+
SIGHTMAP_INIT_PROJECT
|
|
741
|
+
];
|
|
742
|
+
}
|
|
743
|
+
/** Full async list including upstream-proxied tools, if configured. */
|
|
744
|
+
async listToolsAsync() {
|
|
745
|
+
const own = [
|
|
746
|
+
SIGHTMAP_MATCH,
|
|
747
|
+
SIGHTMAP_LIST_VIEWS,
|
|
748
|
+
SIGHTMAP_GET_VIEW,
|
|
749
|
+
SIGHTMAP_CHECK,
|
|
750
|
+
SIGHTMAP_UPDATE_VIEW,
|
|
751
|
+
SIGHTMAP_INIT_PROJECT
|
|
752
|
+
];
|
|
753
|
+
if (this.upstream !== void 0) {
|
|
754
|
+
own.push(SIGHTMAP_SNAPSHOT, SIGHTMAP_ACT, SIGHTMAP_NETWORK_REQUESTS);
|
|
755
|
+
}
|
|
756
|
+
if (this.upstream === void 0) return own;
|
|
757
|
+
const upstreamTools = await this.upstream.listTools();
|
|
758
|
+
const shadowed = SHADOWED_UPSTREAM_TOOLS;
|
|
759
|
+
const filtered = upstreamTools.filter((t) => !shadowed.has(t.name));
|
|
760
|
+
return [...own, ...filtered];
|
|
761
|
+
}
|
|
762
|
+
/** All prompts the server advertises. Static; no upstream involvement. */
|
|
763
|
+
listPrompts() {
|
|
764
|
+
return PROMPT_REGISTRY.map((p) => p.definition);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Resolve a prompt by name, validate args, and produce the prompt result
|
|
768
|
+
* (the messages the agent will inject). Throws on unknown names or on a
|
|
769
|
+
* handler that itself throws (e.g., bad phase value).
|
|
770
|
+
*/
|
|
771
|
+
getPrompt(name, args) {
|
|
772
|
+
const entry = PROMPT_REGISTRY.find(
|
|
773
|
+
(p) => p.definition.name === name
|
|
774
|
+
);
|
|
775
|
+
if (entry === void 0) throw new Error(`unknown prompt: ${name}`);
|
|
776
|
+
return entry.handler(args);
|
|
777
|
+
}
|
|
778
|
+
async callTool(name, args) {
|
|
779
|
+
if (isSightmapTool(name)) {
|
|
780
|
+
switch (name) {
|
|
781
|
+
case "sightmap_match":
|
|
782
|
+
return this.callSightmapMatch(args);
|
|
783
|
+
case "sightmap_list_views":
|
|
784
|
+
return this.callSightmapListViews(args);
|
|
785
|
+
case "sightmap_get_view":
|
|
786
|
+
return this.callSightmapGetView(args);
|
|
787
|
+
case "sightmap_check":
|
|
788
|
+
return this.callSightmapCheck(args);
|
|
789
|
+
case "sightmap_update_view":
|
|
790
|
+
return this.callSightmapUpdateView(args);
|
|
791
|
+
case "sightmap_init_project":
|
|
792
|
+
return this.callSightmapInitProject(args);
|
|
793
|
+
case "sightmap_snapshot":
|
|
794
|
+
return this.callSightmapSnapshot();
|
|
795
|
+
case "sightmap_act":
|
|
796
|
+
return this.callSightmapAct(args);
|
|
797
|
+
case "sightmap_network_requests":
|
|
798
|
+
return this.callSightmapNetworkRequests(args);
|
|
799
|
+
default:
|
|
800
|
+
return errorResult(`Unknown sightmap tool: ${name}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (this.upstream !== void 0) {
|
|
804
|
+
return this.upstream.callTool(name, args);
|
|
805
|
+
}
|
|
806
|
+
return errorResult(`Unknown tool: ${name}`);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Tag a single element matching `selector` with a unique
|
|
810
|
+
* `data-sm-act-target` attribute and return a CSS selector that resolves
|
|
811
|
+
* to exactly that element. The selection mode picks WHICH match to tag:
|
|
812
|
+
* - `{ kind: "index", index }` — pick the Nth match (default).
|
|
813
|
+
* - `{ kind: "containingText", text }` — pick the first match whose
|
|
814
|
+
* `textContent` contains `text` (case-insensitive).
|
|
815
|
+
*
|
|
816
|
+
* Returns null if no candidate is selected (index out of range, or no
|
|
817
|
+
* element's text contains the needle).
|
|
818
|
+
*/
|
|
819
|
+
async tagSingleMatch(selector, selection) {
|
|
820
|
+
if (this.upstream === void 0) return null;
|
|
821
|
+
const marker = `sm-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
|
|
822
|
+
const pickExpr = selection.kind === "index" ? `els[${selection.index}]` : `els.find((el) => (el.textContent || "").toLowerCase().includes(${JSON.stringify(selection.text.toLowerCase())}))`;
|
|
823
|
+
const fnBody = `() => {
|
|
824
|
+
const els = Array.from(document.querySelectorAll(${JSON.stringify(selector)}));
|
|
825
|
+
const target = ${pickExpr};
|
|
826
|
+
if (!target) return null;
|
|
827
|
+
target.setAttribute("data-sm-act-target", ${JSON.stringify(marker)});
|
|
828
|
+
return "[data-sm-act-target=\\"" + ${JSON.stringify(marker)} + "\\"]";
|
|
829
|
+
}`;
|
|
830
|
+
const r = await this.upstream.callTool("browser_evaluate", { function: fnBody });
|
|
831
|
+
if (r.isError === true) return null;
|
|
832
|
+
const text = r.content.map((b) => b.text).join("\n");
|
|
833
|
+
const decoded = decodeMcpEvalResult(text);
|
|
834
|
+
if (typeof decoded !== "string") return null;
|
|
835
|
+
if (!decoded.includes(`data-sm-act-target="${marker}"`)) return null;
|
|
836
|
+
return decoded;
|
|
837
|
+
}
|
|
838
|
+
async callSightmapNetworkRequests(args) {
|
|
839
|
+
if (this.upstream === void 0) {
|
|
840
|
+
return errorResult(
|
|
841
|
+
"sightmap_network_requests: requires an upstream (e.g., @playwright/mcp)."
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const upstreamArgs = {};
|
|
845
|
+
if (typeof args["static"] === "boolean") upstreamArgs["static"] = args["static"];
|
|
846
|
+
if (typeof args["filter"] === "string") upstreamArgs["filter"] = args["filter"];
|
|
847
|
+
const r = await this.upstream.callTool("browser_network_requests", upstreamArgs);
|
|
848
|
+
if (r.isError === true) {
|
|
849
|
+
return r;
|
|
850
|
+
}
|
|
851
|
+
const text = r.content.map((b) => b.text).join("\n");
|
|
852
|
+
const parsed = parseNetworkRequestsText(text);
|
|
853
|
+
const annotated = annotateNetworkRequests(this.sightmap, parsed);
|
|
854
|
+
return {
|
|
855
|
+
content: [{ type: "text", text: JSON.stringify({ requests: annotated }, null, 2) }]
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
async callSightmapAct(args) {
|
|
859
|
+
if (this.upstream === void 0) {
|
|
860
|
+
return errorResult(
|
|
861
|
+
"sightmap_act: requires an upstream (e.g., @playwright/mcp) to be configured."
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
const hasComponent = typeof args["componentName"] === "string" && args["componentName"].length > 0;
|
|
865
|
+
const hasSelector = typeof args["selector"] === "string" && args["selector"].length > 0;
|
|
866
|
+
if (hasComponent && hasSelector) {
|
|
867
|
+
return errorResult(
|
|
868
|
+
"sightmap_act: `componentName` and `selector` are mutually exclusive \u2014 pass one, not both."
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
if (!hasComponent && !hasSelector) {
|
|
872
|
+
return errorResult(
|
|
873
|
+
"sightmap_act: pass either `componentName` (preferred) or `selector` (escape hatch for off-sightmap targets)."
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
if (typeof args["action"] !== "string") {
|
|
877
|
+
return errorResult("sightmap_act: required argument `action` (string) is missing.");
|
|
878
|
+
}
|
|
879
|
+
const action = args["action"];
|
|
880
|
+
const validActions = /* @__PURE__ */ new Set(["click", "type", "hover"]);
|
|
881
|
+
if (!validActions.has(action)) {
|
|
882
|
+
return errorResult(
|
|
883
|
+
`sightmap_act: unsupported action "${action}". Supported: click, type, hover.`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (action === "type" && typeof args["text"] !== "string") {
|
|
887
|
+
return errorResult("sightmap_act: action='type' requires `text` (string).");
|
|
888
|
+
}
|
|
889
|
+
const hasInstance = typeof args["instance"] === "number";
|
|
890
|
+
const hasContainingText = typeof args["containingText"] === "string" && args["containingText"].length > 0;
|
|
891
|
+
if (hasInstance && hasContainingText) {
|
|
892
|
+
return errorResult(
|
|
893
|
+
"sightmap_act: `instance` and `containingText` are mutually exclusive \u2014 pass one, not both."
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
let targetSelector;
|
|
897
|
+
let targetLabel;
|
|
898
|
+
if (hasComponent) {
|
|
899
|
+
const resolved2 = resolveSightmapAct(this.sightmap, {
|
|
900
|
+
componentName: args["componentName"]
|
|
901
|
+
});
|
|
902
|
+
if (resolved2.kind === "error") {
|
|
903
|
+
return errorResult("sightmap_act: " + resolved2.message);
|
|
904
|
+
}
|
|
905
|
+
targetSelector = resolved2.selector;
|
|
906
|
+
targetLabel = resolved2.componentName;
|
|
907
|
+
} else {
|
|
908
|
+
targetSelector = args["selector"];
|
|
909
|
+
targetLabel = `selector:${targetSelector}`;
|
|
910
|
+
}
|
|
911
|
+
const resolved = { kind: "ok", componentName: targetLabel, selector: targetSelector };
|
|
912
|
+
const selection = hasContainingText ? { kind: "containingText", text: args["containingText"] } : { kind: "index", index: hasInstance ? Math.floor(args["instance"]) : 0 };
|
|
913
|
+
const taggedSelector = await this.tagSingleMatch(resolved.selector, selection);
|
|
914
|
+
if (taggedSelector === null) {
|
|
915
|
+
const reason = selection.kind === "containingText" ? `no instance of "${resolved.componentName}" contains text ${JSON.stringify(selection.text)}` : `could not tag the element at instance=${selection.index} for component "${resolved.componentName}"`;
|
|
916
|
+
return errorResult(
|
|
917
|
+
`sightmap_act: ${reason}. The selector ${JSON.stringify(resolved.selector)} may not resolve any matching elements on the current page.`
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const baseArgs = {
|
|
921
|
+
target: taggedSelector,
|
|
922
|
+
element: resolved.componentName
|
|
923
|
+
};
|
|
924
|
+
let upstreamName;
|
|
925
|
+
let upstreamArgs;
|
|
926
|
+
switch (action) {
|
|
927
|
+
case "click":
|
|
928
|
+
upstreamName = "browser_click";
|
|
929
|
+
upstreamArgs = baseArgs;
|
|
930
|
+
break;
|
|
931
|
+
case "hover":
|
|
932
|
+
upstreamName = "browser_hover";
|
|
933
|
+
upstreamArgs = baseArgs;
|
|
934
|
+
break;
|
|
935
|
+
case "type":
|
|
936
|
+
upstreamName = "browser_type";
|
|
937
|
+
upstreamArgs = {
|
|
938
|
+
...baseArgs,
|
|
939
|
+
text: args["text"],
|
|
940
|
+
...args["submit"] === true ? { submit: true } : {}
|
|
941
|
+
};
|
|
942
|
+
break;
|
|
943
|
+
default:
|
|
944
|
+
return errorResult(`sightmap_act: unsupported action "${action}".`);
|
|
945
|
+
}
|
|
946
|
+
return this.upstream.callTool(upstreamName, upstreamArgs);
|
|
947
|
+
}
|
|
948
|
+
async callSightmapSnapshot() {
|
|
949
|
+
if (this.upstream === void 0) {
|
|
950
|
+
return errorResult(
|
|
951
|
+
"sightmap_snapshot: requires an upstream (e.g., @playwright/mcp) to be configured."
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
const snapResult = await this.upstream.callTool("browser_snapshot", {});
|
|
955
|
+
if (snapResult.isError === true) {
|
|
956
|
+
return errorResult(
|
|
957
|
+
"sightmap_snapshot: upstream browser_snapshot failed: " + (snapResult.content[0]?.text ?? "unknown error")
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const ariaSnapshotText = snapResult.content.map((b) => b.text).join("\n");
|
|
961
|
+
const currentUrl = parsePageUrl(ariaSnapshotText) ?? "";
|
|
962
|
+
const matchResult = sightmapMatch4(this.sightmap, { url: currentUrl });
|
|
963
|
+
const componentSelectors = matchResult.components.map((c) => ({
|
|
964
|
+
name: c.name,
|
|
965
|
+
selector: c.selector
|
|
966
|
+
}));
|
|
967
|
+
const evalResult = await this.upstream.callTool("browser_evaluate", {
|
|
968
|
+
function: buildInPageEvalFunction(componentSelectors)
|
|
969
|
+
});
|
|
970
|
+
if (evalResult.isError === true) {
|
|
971
|
+
return errorResult(
|
|
972
|
+
"sightmap_snapshot: upstream browser_evaluate failed: " + (evalResult.content[0]?.text ?? "unknown error")
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const inPageMatches = parseInPageEvalResult(
|
|
976
|
+
evalResult.content.map((b) => b.text).join("\n")
|
|
977
|
+
);
|
|
978
|
+
const response = buildSightmapSnapshotResponse({
|
|
979
|
+
sightmap: this.sightmap,
|
|
980
|
+
currentUrl,
|
|
981
|
+
ariaSnapshotText,
|
|
982
|
+
inPageMatches
|
|
983
|
+
});
|
|
984
|
+
return {
|
|
985
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
callSightmapMatch(args) {
|
|
989
|
+
if (typeof args["url"] !== "string" || args["url"].length === 0) {
|
|
990
|
+
return errorResult("sightmap_match: required argument `url` (string) is missing or empty.");
|
|
991
|
+
}
|
|
992
|
+
const input = { url: args["url"] };
|
|
993
|
+
if (typeof args["method"] === "string") {
|
|
994
|
+
input.method = args["method"];
|
|
995
|
+
}
|
|
996
|
+
const output = handleSightmapMatch(this.sightmap, input);
|
|
997
|
+
return {
|
|
998
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async callSightmapGetView(args) {
|
|
1002
|
+
if (this.curateRoot === void 0) {
|
|
1003
|
+
return errorResult(
|
|
1004
|
+
"sightmap_get_view: no writable sightmap dir configured (pass --curate-root or --sightmap-dir)."
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (typeof args["name"] !== "string" || args["name"].length === 0) {
|
|
1008
|
+
return errorResult("sightmap_get_view: required argument `name` (string) is missing or empty.");
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
const result = await handleGetView({
|
|
1012
|
+
sightmapDir: this.curateRoot,
|
|
1013
|
+
name: args["name"]
|
|
1014
|
+
});
|
|
1015
|
+
return {
|
|
1016
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1017
|
+
};
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async callSightmapInitProject(args) {
|
|
1023
|
+
const input = {};
|
|
1024
|
+
if (typeof args["dir"] === "string") input.dir = args["dir"];
|
|
1025
|
+
if (args["force"] === true) input.force = true;
|
|
1026
|
+
try {
|
|
1027
|
+
const result = await handleInitProject(input);
|
|
1028
|
+
return {
|
|
1029
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1030
|
+
};
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async callSightmapUpdateView(args) {
|
|
1036
|
+
if (this.curateRoot === void 0) {
|
|
1037
|
+
return errorResult(
|
|
1038
|
+
"sightmap_update_view: no writable sightmap dir configured (pass --curate-root or --sightmap-dir)."
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
if (typeof args["name"] !== "string" || args["name"].length === 0) {
|
|
1042
|
+
return errorResult(
|
|
1043
|
+
"sightmap_update_view: required argument `name` (string) is missing or empty."
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (args["patch"] === null || typeof args["patch"] !== "object" || Array.isArray(args["patch"])) {
|
|
1047
|
+
return errorResult(
|
|
1048
|
+
"sightmap_update_view: required argument `patch` (object) is missing."
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
try {
|
|
1052
|
+
const result = await handleUpdateView({
|
|
1053
|
+
sightmapDir: this.curateRoot,
|
|
1054
|
+
name: args["name"],
|
|
1055
|
+
patch: args["patch"]
|
|
1056
|
+
});
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1059
|
+
};
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
async callSightmapCheck(args) {
|
|
1065
|
+
const input = {};
|
|
1066
|
+
if (args["level"] === "schema" || args["level"] === "quality") {
|
|
1067
|
+
input.level = args["level"];
|
|
1068
|
+
} else if (args["level"] !== void 0) {
|
|
1069
|
+
return errorResult(
|
|
1070
|
+
`sightmap_check: level must be "schema" or "quality".`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const result = await handleCheck(this.sightmap, input);
|
|
1074
|
+
return {
|
|
1075
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
callSightmapListViews(args) {
|
|
1079
|
+
const input = {};
|
|
1080
|
+
if (args["detail"] === "summary" || args["detail"] === "full") {
|
|
1081
|
+
input.detail = args["detail"];
|
|
1082
|
+
} else if (args["detail"] !== void 0) {
|
|
1083
|
+
return errorResult(
|
|
1084
|
+
`sightmap_list_views: detail must be "summary" or "full".`
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const output = handleListViews(this.sightmap, input);
|
|
1088
|
+
return {
|
|
1089
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
function errorResult(text) {
|
|
1094
|
+
return {
|
|
1095
|
+
content: [{ type: "text", text }],
|
|
1096
|
+
isError: true
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function isSightmapTool(name) {
|
|
1100
|
+
return name.startsWith("sightmap_");
|
|
1101
|
+
}
|
|
1102
|
+
function parsePageUrl(text) {
|
|
1103
|
+
const m = /Page URL:\s*(\S+)/.exec(text);
|
|
1104
|
+
return m?.[1] ?? null;
|
|
1105
|
+
}
|
|
1106
|
+
function buildInPageEvalFunction(components) {
|
|
1107
|
+
const componentsJson = JSON.stringify(components);
|
|
1108
|
+
return `() => {
|
|
1109
|
+
const components = ${componentsJson};
|
|
1110
|
+
const matches = components.map((c) => {
|
|
1111
|
+
const selectors = c.selector || [];
|
|
1112
|
+
const seen = new Set();
|
|
1113
|
+
const elements = [];
|
|
1114
|
+
for (const sel of selectors) {
|
|
1115
|
+
try {
|
|
1116
|
+
const found = document.querySelectorAll(sel);
|
|
1117
|
+
for (const el of Array.from(found)) {
|
|
1118
|
+
if (!seen.has(el)) {
|
|
1119
|
+
seen.add(el);
|
|
1120
|
+
elements.push(el);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
} catch {
|
|
1124
|
+
// Invalid selector \u2014 skip silently.
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const first = elements[0];
|
|
1128
|
+
let samplePosition;
|
|
1129
|
+
if (first && typeof first.getBoundingClientRect === "function") {
|
|
1130
|
+
const r = first.getBoundingClientRect();
|
|
1131
|
+
samplePosition = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
name: c.name,
|
|
1135
|
+
selector: selectors,
|
|
1136
|
+
matchCount: elements.length,
|
|
1137
|
+
samplePosition,
|
|
1138
|
+
};
|
|
1139
|
+
});
|
|
1140
|
+
return { url: window.location.href, matches };
|
|
1141
|
+
}`;
|
|
1142
|
+
}
|
|
1143
|
+
function parseInPageEvalResult(text) {
|
|
1144
|
+
const afterResult = text.split(/^###\s*Result/im)[1] ?? text;
|
|
1145
|
+
const start = afterResult.indexOf("{");
|
|
1146
|
+
if (start < 0) return [];
|
|
1147
|
+
for (let end = afterResult.length; end > start; end--) {
|
|
1148
|
+
try {
|
|
1149
|
+
const parsed = JSON.parse(afterResult.slice(start, end));
|
|
1150
|
+
if (Array.isArray(parsed.matches)) {
|
|
1151
|
+
return parsed.matches.filter(isInPageMatch);
|
|
1152
|
+
}
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return [];
|
|
1157
|
+
}
|
|
1158
|
+
function decodeMcpEvalResult(text) {
|
|
1159
|
+
const afterResult = text.split(/^###\s*Result/im)[1] ?? "";
|
|
1160
|
+
const trimmed = afterResult.trim();
|
|
1161
|
+
if (trimmed.length === 0) return void 0;
|
|
1162
|
+
for (let end = trimmed.length; end > 0; end--) {
|
|
1163
|
+
try {
|
|
1164
|
+
return JSON.parse(trimmed.slice(0, end));
|
|
1165
|
+
} catch {
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return void 0;
|
|
1169
|
+
}
|
|
1170
|
+
function isInPageMatch(value) {
|
|
1171
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1172
|
+
const v = value;
|
|
1173
|
+
return typeof v["name"] === "string" && typeof v["matchCount"] === "number";
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/upstream.ts
|
|
1177
|
+
var McpClientUpstream = class {
|
|
1178
|
+
constructor(client) {
|
|
1179
|
+
this.client = client;
|
|
1180
|
+
}
|
|
1181
|
+
client;
|
|
1182
|
+
async listTools() {
|
|
1183
|
+
const r = await this.client.listTools();
|
|
1184
|
+
return r.tools.map((t) => ({
|
|
1185
|
+
name: t.name,
|
|
1186
|
+
description: t.description ?? "",
|
|
1187
|
+
inputSchema: t.inputSchema
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
async callTool(name, args) {
|
|
1191
|
+
const raw = await this.client.callTool({
|
|
1192
|
+
name,
|
|
1193
|
+
arguments: args
|
|
1194
|
+
});
|
|
1195
|
+
const content = (raw.content ?? []).map((block) => {
|
|
1196
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1197
|
+
return { type: "text", text: block.text };
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
type: "text",
|
|
1201
|
+
text: stringifyNonTextBlock(block)
|
|
1202
|
+
};
|
|
1203
|
+
});
|
|
1204
|
+
return {
|
|
1205
|
+
content: content.length > 0 ? content : [{ type: "text", text: "" }],
|
|
1206
|
+
...raw.isError === true ? { isError: true } : {}
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
function stringifyNonTextBlock(block) {
|
|
1211
|
+
const parts = [`[${block.type}]`];
|
|
1212
|
+
if (block.mimeType) parts.push(`mimeType=${block.mimeType}`);
|
|
1213
|
+
if (typeof block.data === "string") {
|
|
1214
|
+
const preview = block.data.length > 64 ? block.data.slice(0, 64) + "\u2026" : block.data;
|
|
1215
|
+
parts.push(`data=${preview}`);
|
|
1216
|
+
}
|
|
1217
|
+
return parts.join(" ");
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/mcpServer.ts
|
|
1221
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1222
|
+
import {
|
|
1223
|
+
CallToolRequestSchema,
|
|
1224
|
+
ListToolsRequestSchema,
|
|
1225
|
+
ListPromptsRequestSchema,
|
|
1226
|
+
GetPromptRequestSchema
|
|
1227
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1228
|
+
function buildMcpServer(dispatcher) {
|
|
1229
|
+
const server = new Server(
|
|
1230
|
+
{ name: "sightmap-mcp", version: "0.0.0" },
|
|
1231
|
+
{ capabilities: { tools: {}, prompts: {} } }
|
|
1232
|
+
);
|
|
1233
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1234
|
+
const tools = await dispatcher.listToolsAsync();
|
|
1235
|
+
return { tools };
|
|
1236
|
+
});
|
|
1237
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
1238
|
+
const name = req.params.name;
|
|
1239
|
+
const args = req.params.arguments ?? {};
|
|
1240
|
+
const result = await dispatcher.callTool(name, args);
|
|
1241
|
+
return result;
|
|
1242
|
+
});
|
|
1243
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1244
|
+
return { prompts: dispatcher.listPrompts() };
|
|
1245
|
+
});
|
|
1246
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
1247
|
+
const name = req.params.name;
|
|
1248
|
+
const args = req.params.arguments ?? {};
|
|
1249
|
+
return dispatcher.getPrompt(name, args);
|
|
1250
|
+
});
|
|
1251
|
+
return server;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// src/cli.ts
|
|
1255
|
+
function parseArgv(argv) {
|
|
1256
|
+
const collectedDirs = [];
|
|
1257
|
+
let explicitCurateRoot;
|
|
1258
|
+
const out = {
|
|
1259
|
+
sightmapDirs: [],
|
|
1260
|
+
curateRoot: "",
|
|
1261
|
+
upstreamCommand: "npx",
|
|
1262
|
+
upstreamArgs: ["-y", "@playwright/mcp@latest"]
|
|
1263
|
+
};
|
|
1264
|
+
let i = 0;
|
|
1265
|
+
while (i < argv.length) {
|
|
1266
|
+
const a = argv[i] ?? "";
|
|
1267
|
+
if (a === "--sightmap-dir") {
|
|
1268
|
+
const v = argv[++i];
|
|
1269
|
+
if (v !== void 0) collectedDirs.push(v);
|
|
1270
|
+
} else if (a.startsWith("--sightmap-dir=")) {
|
|
1271
|
+
collectedDirs.push(a.slice("--sightmap-dir=".length));
|
|
1272
|
+
} else if (a === "--curate-root") {
|
|
1273
|
+
const v = argv[++i];
|
|
1274
|
+
if (v !== void 0) explicitCurateRoot = v;
|
|
1275
|
+
} else if (a.startsWith("--curate-root=")) {
|
|
1276
|
+
explicitCurateRoot = a.slice("--curate-root=".length);
|
|
1277
|
+
} else if (a === "--upstream-command") {
|
|
1278
|
+
out.upstreamCommand = argv[++i] ?? out.upstreamCommand;
|
|
1279
|
+
} else if (a === "--") {
|
|
1280
|
+
out.upstreamArgs = argv.slice(i + 1);
|
|
1281
|
+
i = argv.length;
|
|
1282
|
+
} else if (a === "--help" || a === "-h") {
|
|
1283
|
+
printHelp();
|
|
1284
|
+
process.exit(0);
|
|
1285
|
+
} else {
|
|
1286
|
+
process.stderr.write(`sightmap-mcp: unknown argument: ${a}
|
|
1287
|
+
`);
|
|
1288
|
+
printHelp();
|
|
1289
|
+
process.exit(2);
|
|
1290
|
+
}
|
|
1291
|
+
i++;
|
|
1292
|
+
}
|
|
1293
|
+
out.sightmapDirs = collectedDirs.length > 0 ? collectedDirs : [".sightmap"];
|
|
1294
|
+
out.curateRoot = explicitCurateRoot !== void 0 ? resolve(explicitCurateRoot) : resolve(out.sightmapDirs[0]);
|
|
1295
|
+
return out;
|
|
1296
|
+
}
|
|
1297
|
+
function printHelp() {
|
|
1298
|
+
process.stderr.write(
|
|
1299
|
+
[
|
|
1300
|
+
"Usage: sightmap-mcp [options]",
|
|
1301
|
+
"",
|
|
1302
|
+
" --sightmap-dir <path> Directory of sightmap YAML files. Repeatable.",
|
|
1303
|
+
" Default: .sightmap",
|
|
1304
|
+
" --curate-root <path> Writable .sightmap/ for curation tools.",
|
|
1305
|
+
" Default: the first --sightmap-dir.",
|
|
1306
|
+
" --upstream-command <cmd> Command to spawn the upstream MCP server. Default: npx",
|
|
1307
|
+
" -- <args...> Args for the upstream command. Default: -y @playwright/mcp@latest",
|
|
1308
|
+
" -h, --help Show this help and exit",
|
|
1309
|
+
""
|
|
1310
|
+
].join("\n")
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
async function main() {
|
|
1314
|
+
const args = parseArgv(process.argv.slice(2));
|
|
1315
|
+
const { mergeSightmaps: mergeSightmaps2 } = await Promise.resolve().then(() => (init_loadMerged(), loadMerged_exports));
|
|
1316
|
+
const sightmap = await mergeSightmaps2(args.sightmapDirs.map((d) => resolve(d)));
|
|
1317
|
+
if (sightmap.diagnostics.some((d) => d.severity === "error")) {
|
|
1318
|
+
process.stderr.write(
|
|
1319
|
+
`sightmap-mcp: ${sightmap.diagnostics.length} diagnostic(s) loading sightmap; continuing
|
|
1320
|
+
`
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
const upstreamTransport = new StdioClientTransport({
|
|
1324
|
+
command: args.upstreamCommand,
|
|
1325
|
+
args: args.upstreamArgs
|
|
1326
|
+
});
|
|
1327
|
+
const upstreamClient = new Client(
|
|
1328
|
+
{ name: "sightmap-mcp-bridge", version: "0.0.0" },
|
|
1329
|
+
{ capabilities: {} }
|
|
1330
|
+
);
|
|
1331
|
+
await upstreamClient.connect(upstreamTransport);
|
|
1332
|
+
const dispatcher = new SightmapMcpServer({
|
|
1333
|
+
sightmap,
|
|
1334
|
+
upstream: new McpClientUpstream(upstreamClient),
|
|
1335
|
+
curateRoot: args.curateRoot
|
|
1336
|
+
});
|
|
1337
|
+
const server = buildMcpServer(dispatcher);
|
|
1338
|
+
await server.connect(new StdioServerTransport());
|
|
1339
|
+
const cleanup = async () => {
|
|
1340
|
+
try {
|
|
1341
|
+
await upstreamClient.close();
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
process.exit(0);
|
|
1345
|
+
};
|
|
1346
|
+
process.on("SIGINT", cleanup);
|
|
1347
|
+
process.on("SIGTERM", cleanup);
|
|
1348
|
+
}
|
|
1349
|
+
var isMain = process.argv[1] !== void 0 && (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("\\cli.js"));
|
|
1350
|
+
if (isMain) {
|
|
1351
|
+
main().catch((err) => {
|
|
1352
|
+
const msg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
1353
|
+
process.stderr.write(`sightmap-mcp: fatal: ${msg}
|
|
1354
|
+
`);
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
export {
|
|
1359
|
+
parseArgv
|
|
1360
|
+
};
|
|
1361
|
+
//# sourceMappingURL=cli.js.map
|