clipqa-mcp 0.2.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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/build/artifacts.d.ts +4 -0
  4. package/build/artifacts.d.ts.map +1 -0
  5. package/build/artifacts.js +24 -0
  6. package/build/artifacts.js.map +1 -0
  7. package/build/backends.d.ts +16 -0
  8. package/build/backends.d.ts.map +1 -0
  9. package/build/backends.js +48 -0
  10. package/build/backends.js.map +1 -0
  11. package/build/config.d.ts +7 -0
  12. package/build/config.d.ts.map +1 -0
  13. package/build/config.js +15 -0
  14. package/build/config.js.map +1 -0
  15. package/build/index.d.ts +3 -0
  16. package/build/index.d.ts.map +1 -0
  17. package/build/index.js +25 -0
  18. package/build/index.js.map +1 -0
  19. package/build/media.d.ts +13 -0
  20. package/build/media.d.ts.map +1 -0
  21. package/build/media.js +139 -0
  22. package/build/media.js.map +1 -0
  23. package/build/process-runner.d.ts +9 -0
  24. package/build/process-runner.d.ts.map +1 -0
  25. package/build/process-runner.js +25 -0
  26. package/build/process-runner.js.map +1 -0
  27. package/build/recorder.d.ts +42 -0
  28. package/build/recorder.d.ts.map +1 -0
  29. package/build/recorder.js +272 -0
  30. package/build/recorder.js.map +1 -0
  31. package/build/replay.d.ts +10 -0
  32. package/build/replay.d.ts.map +1 -0
  33. package/build/replay.js +39 -0
  34. package/build/replay.js.map +1 -0
  35. package/build/sampling.d.ts +13 -0
  36. package/build/sampling.d.ts.map +1 -0
  37. package/build/sampling.js +52 -0
  38. package/build/sampling.js.map +1 -0
  39. package/build/server.d.ts +5 -0
  40. package/build/server.d.ts.map +1 -0
  41. package/build/server.js +360 -0
  42. package/build/server.js.map +1 -0
  43. package/build/session-store.d.ts +16 -0
  44. package/build/session-store.d.ts.map +1 -0
  45. package/build/session-store.js +93 -0
  46. package/build/session-store.js.map +1 -0
  47. package/build/types.d.ts +46 -0
  48. package/build/types.d.ts.map +1 -0
  49. package/build/types.js +2 -0
  50. package/build/types.js.map +1 -0
  51. package/build/visual.d.ts +62 -0
  52. package/build/visual.d.ts.map +1 -0
  53. package/build/visual.js +145 -0
  54. package/build/visual.js.map +1 -0
  55. package/build/windows.d.ts +4 -0
  56. package/build/windows.d.ts.map +1 -0
  57. package/build/windows.js +60 -0
  58. package/build/windows.js.map +1 -0
  59. package/docs/assets/banner.png +0 -0
  60. package/package.json +67 -0
  61. package/scripts/install-wgc.ps1 +40 -0
@@ -0,0 +1,62 @@
1
+ import { MediaProcessor } from "./media.js";
2
+ export interface Region {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface Difference {
9
+ meanDifference: number;
10
+ peakTileDifference: number;
11
+ ssim: number;
12
+ changedFraction: number;
13
+ }
14
+ export declare function difference(a: Uint8Array, b: Uint8Array, width: number, height: number, ignore?: Region[]): Difference;
15
+ export interface VisualIndex {
16
+ fps: number;
17
+ decodedFrames: number;
18
+ width: number;
19
+ height: number;
20
+ changes: Array<{
21
+ timestamp: number;
22
+ score: number;
23
+ }>;
24
+ warnings: string[];
25
+ }
26
+ export declare function indexVideo(ffmpeg: string, video: string, start: number, end: number, fps?: number): Promise<VisualIndex>;
27
+ export declare function compareVideos(media: MediaProcessor, before: string, after: string, directory: string, options: {
28
+ beforeStart: number;
29
+ afterStart: number;
30
+ seconds: number;
31
+ samples: number;
32
+ threshold: number;
33
+ ignore: Region[];
34
+ }): Promise<{
35
+ schemaVersion: number;
36
+ options: {
37
+ beforeStart: number;
38
+ afterStart: number;
39
+ seconds: number;
40
+ samples: number;
41
+ threshold: number;
42
+ ignore: Region[];
43
+ };
44
+ changedSamples: number;
45
+ rows: (Difference & {
46
+ beforeSeconds: number;
47
+ afterSeconds: number;
48
+ score: number;
49
+ changed: boolean;
50
+ })[];
51
+ note: string;
52
+ previewSample: (Difference & {
53
+ beforeSeconds: number;
54
+ afterSeconds: number;
55
+ score: number;
56
+ changed: boolean;
57
+ }) | undefined;
58
+ previewPath: string;
59
+ reportPath: string;
60
+ jsonPath: string;
61
+ }>;
62
+ //# sourceMappingURL=visual.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,MAAM;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/E,MAAM,WAAW,UAAU;IAAG,cAAc,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE;AAGzH,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO,GAAG,UAAU,CAsBzH;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAClE,OAAO,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;CAC1E;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CA8C1H;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;;;qBAAnG,MAAM;oBAAc,MAAM;iBAAW,MAAM;iBAAW,MAAM;mBAAa,MAAM;gBAAU,MAAM,EAAE;;;;uBAGpL,MAAM;sBAAgB,MAAM;eAAS,MAAM;iBAAW,OAAO;;;;;;;;;;;;GAiC9G"}
@@ -0,0 +1,145 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import sharp from "sharp";
5
+ import { randomUUID } from "node:crypto";
6
+ // Local SSIM plus RGB error catches both structural and flat-color changes.
7
+ export function difference(a, b, width, height, ignore = []) {
8
+ if (a.length !== width * height * 3 || b.length !== a.length)
9
+ throw new Error("Frame dimensions must match.");
10
+ let sum = 0, pixels = 0, changed = 0, peak = 0, ssimSum = 0, tiles = 0;
11
+ for (let y = 0; y < height; y += 8)
12
+ for (let x = 0; x < width; x += 8) {
13
+ let n = 0, sx = 0, sy = 0, xx = 0, yy = 0, xy = 0, delta = 0;
14
+ for (let j = y; j < Math.min(y + 8, height); j++)
15
+ for (let i = x; i < Math.min(x + 8, width); i++) {
16
+ if (ignore.some(r => i / width >= r.x && i / width < r.x + r.width && j / height >= r.y && j / height < r.y + r.height))
17
+ continue;
18
+ const k = (j * width + i) * 3;
19
+ const u = .2126 * a[k] + .7152 * a[k + 1] + .0722 * a[k + 2];
20
+ const v = .2126 * b[k] + .7152 * b[k + 1] + .0722 * b[k + 2];
21
+ const d = (Math.abs(a[k] - b[k]) + Math.abs(a[k + 1] - b[k + 1]) + Math.abs(a[k + 2] - b[k + 2])) / (3 * 255);
22
+ delta += d;
23
+ sum += d;
24
+ pixels++;
25
+ if (d > .04)
26
+ changed++;
27
+ n++;
28
+ sx += u;
29
+ sy += v;
30
+ xx += u * u;
31
+ yy += v * v;
32
+ xy += u * v;
33
+ }
34
+ if (!n)
35
+ continue;
36
+ const mx = sx / n, my = sy / n;
37
+ const vx = Math.max(0, xx / n - mx * mx), vy = Math.max(0, yy / n - my * my), cov = xy / n - mx * my;
38
+ ssimSum += ((2 * mx * my + 6.5025) * (2 * cov + 58.5225)) / ((mx * mx + my * my + 6.5025) * (vx + vy + 58.5225));
39
+ tiles++;
40
+ peak = Math.max(peak, delta / n);
41
+ }
42
+ if (!pixels)
43
+ throw new Error("Ignore regions cover the entire frame.");
44
+ return { meanDifference: sum / pixels, peakTileDifference: peak, ssim: ssimSum / tiles, changedFraction: changed / pixels };
45
+ }
46
+ export async function indexVideo(ffmpeg, video, start, end, fps = 15) {
47
+ if (!(end > start) || end - start > 3600 || fps < 1 || fps > 60)
48
+ throw new Error("Index a valid range up to one hour at 1–60 FPS.");
49
+ const stat = await fs.stat(video);
50
+ const cache = `${video}.index-${start}-${end}-${fps}.json`;
51
+ const key = `${stat.size}:${stat.mtimeMs}:v1`;
52
+ const cached = await fs.readFile(cache, "utf8").then(JSON.parse).catch(() => undefined);
53
+ if (cached?.key === key)
54
+ return cached.index;
55
+ const width = 160, height = 90, bytes = width * height * 3;
56
+ const index = await new Promise((resolve, reject) => {
57
+ const child = spawn(ffmpeg, ["-hide_banner", "-loglevel", "error", "-ss", String(start), "-protocol_whitelist", "file,pipe", "-i", video, "-t", String(end - start), "-an", "-vf", `fps=${fps},scale=${width}:${height}`, "-pix_fmt", "rgb24", "-f", "rawvideo", "pipe:1"], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
58
+ let pending = Buffer.alloc(0), previous, count = 0, stderr = "", staticFrames = 0, darkFrames = 0;
59
+ const bins = new Map();
60
+ const timeout = setTimeout(() => { child.kill(); reject(new Error("Visual indexing timed out; inspect a shorter range.")); }, 120_000);
61
+ child.stderr.on("data", chunk => { stderr = (stderr + String(chunk)).slice(-2000); });
62
+ child.stdout.on("data", (chunk) => {
63
+ pending = Buffer.concat([pending, chunk]);
64
+ while (pending.length >= bytes) {
65
+ const frame = pending.subarray(0, bytes);
66
+ let brightness = 0;
67
+ for (const value of frame)
68
+ brightness += value;
69
+ if (brightness / bytes < 3)
70
+ darkFrames++;
71
+ if (previous) {
72
+ const d = difference(previous, frame, width, height);
73
+ const score = Math.max(d.peakTileDifference, 1 - d.ssim);
74
+ if (d.meanDifference < .0005)
75
+ staticFrames++;
76
+ if (score >= .035) {
77
+ const timestamp = start + count / fps, bin = Math.floor((timestamp - start) * 2);
78
+ if (!bins.has(bin) || bins.get(bin).score < score)
79
+ bins.set(bin, { timestamp, score });
80
+ }
81
+ }
82
+ previous = Buffer.from(frame);
83
+ pending = pending.subarray(bytes);
84
+ count++;
85
+ }
86
+ });
87
+ child.on("error", e => { clearTimeout(timeout); reject(e); });
88
+ child.on("close", code => {
89
+ clearTimeout(timeout);
90
+ if (code || !count || pending.length)
91
+ return reject(new Error(`Visual indexing failed: ${stderr || "no complete frames"}`));
92
+ const warnings = [];
93
+ if (darkFrames / count > .95)
94
+ warnings.push("Almost all frames are black; check permissions, protected content, and target rendering.");
95
+ if (staticFrames / Math.max(1, count - 1) > .98)
96
+ warnings.push("Nearly static recording: may be an idle application, a frozen capture, or a minimized target. This is not a defect diagnosis.");
97
+ const changes = [...bins.values()].sort((a, b) => b.score - a.score).slice(0, 256).sort((a, b) => a.timestamp - b.timestamp);
98
+ resolve({ fps, decodedFrames: count, width, height, changes, warnings });
99
+ });
100
+ });
101
+ const temp = `${cache}.${randomUUID()}.tmp`;
102
+ await fs.writeFile(temp, JSON.stringify({ key, index }));
103
+ await fs.rename(temp, cache);
104
+ return index;
105
+ }
106
+ export async function compareVideos(media, before, after, directory, options) {
107
+ if (options.seconds <= 0 || options.samples < 2 || options.samples > 60)
108
+ throw new Error("Compare a positive duration with 2–60 samples.");
109
+ await fs.mkdir(directory, { recursive: true });
110
+ const rows = [];
111
+ const htmlRows = [];
112
+ for (let i = 0; i < options.samples; i++) {
113
+ const offset = i * options.seconds / options.samples;
114
+ const a = path.join(directory, `before-${i}.png`), b = path.join(directory, `after-${i}.png`);
115
+ await media.extractFrame(before, options.beforeStart + offset, a, 640);
116
+ await media.extractFrame(after, options.afterStart + offset, b, 640);
117
+ const raw = async (file) => sharp(file).resize(320, 180, { fit: "contain", background: "black" }).removeAlpha().raw().toBuffer();
118
+ const [left, right] = await Promise.all([raw(a), raw(b)]);
119
+ const metrics = difference(left, right, 320, 180, options.ignore);
120
+ const score = Math.max(metrics.peakTileDifference, 1 - metrics.ssim);
121
+ const row = { beforeSeconds: options.beforeStart + offset, afterSeconds: options.afterStart + offset, ...metrics, score, changed: score >= options.threshold };
122
+ rows.push(row);
123
+ const mask = Buffer.alloc(left.length);
124
+ for (let p = 0; p < left.length; p += 3) {
125
+ const x = (p / 3 % 320) / 320, y = Math.floor(p / 3 / 320) / 180;
126
+ const ignored = options.ignore.some(r => x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height);
127
+ const delta = Math.max(Math.abs(left[p] - right[p]), Math.abs(left[p + 1] - right[p + 1]), Math.abs(left[p + 2] - right[p + 2]));
128
+ mask[p] = ignored ? 0 : Math.min(255, delta * 4);
129
+ mask[p + 1] = ignored ? 0 : delta;
130
+ }
131
+ const heatmap = await sharp(mask, { raw: { width: 320, height: 180, channels: 3 } }).png().toBuffer();
132
+ await fs.writeFile(path.join(directory, `difference-${i}.png`), heatmap);
133
+ const [aData, bData] = await Promise.all([fs.readFile(a), fs.readFile(b)]);
134
+ htmlRows.push(`<section><h2>${row.beforeSeconds.toFixed(3)}s → ${row.afterSeconds.toFixed(3)}s · ${row.changed ? "Changed" : "Within threshold"}</h2><p>SSIM ${metrics.ssim.toFixed(4)} · changed pixels ${(metrics.changedFraction * 100).toFixed(2)}%</p><div>${[aData, bData, heatmap].map((data, j) => `<figure><img alt="${["Before", "After", "Difference"][j]}" src="data:image/png;base64,${data.toString("base64")}"><figcaption>${["Before", "After", "Difference"][j]}</figcaption></figure>`).join("")}</div></section>`);
135
+ }
136
+ const report = { schemaVersion: 1, options, changedSamples: rows.filter(row => row.changed).length, rows, note: "Visual differences are candidates for review, not confirmed regressions. Only sampled timestamps are compared; motion between them may be missed." };
137
+ await fs.writeFile(path.join(directory, "report.json"), JSON.stringify(report, null, 2));
138
+ await fs.writeFile(path.join(directory, "report.html"), `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>ClipQA comparison</title><style>body{font:16px system-ui;background:#0d1117;color:#e6edf3;margin:32px}section{border-top:1px solid #30363d;padding:16px 0}div{display:flex;flex-wrap:wrap;gap:12px}figure{margin:0;flex:1;min-width:240px}img{width:100%}figcaption{color:#8b949e}h2{font-size:18px}</style><h1>ClipQA comparison</h1><p>${report.changedSamples}/${rows.length} samples changed. ${report.note}</p>${htmlRows.join("")}</html>`);
139
+ const worst = rows.reduce((best, row, i) => row.score > rows[best].score ? i : best, 0);
140
+ const previewPath = path.join(directory, "preview.jpg");
141
+ const previews = await Promise.all(["before", "after", "difference"].map(async (name, i) => ({ input: await sharp(path.join(directory, `${name}-${worst}.png`)).resize(320, 180, { fit: "contain", background: "black" }).png().toBuffer(), left: i * 320, top: 0 })));
142
+ await sharp({ create: { width: 960, height: 180, channels: 3, background: "black" } }).composite(previews).jpeg({ quality: 85 }).toFile(previewPath);
143
+ return { ...report, previewSample: rows[worst], previewPath, reportPath: path.join(directory, "report.html"), jsonPath: path.join(directory, "report.json") };
144
+ }
145
+ //# sourceMappingURL=visual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual.js","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzC,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,CAAa,EAAE,CAAa,EAAE,KAAa,EAAE,MAAc,EAAE,MAAM,GAAa,EAAE;IAC3G,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC9G,IAAI,GAAG,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC;IACvE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC;YAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE;gBAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBAClG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;wBAAE,SAAS;oBAClI,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;oBAC9B,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;oBAChE,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;oBAChE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;oBACpH,KAAK,IAAI,CAAC,CAAC;oBAAC,GAAG,IAAI,CAAC,CAAC;oBAAC,MAAM,EAAE,CAAC;oBAAC,IAAI,CAAC,GAAG,GAAG;wBAAE,OAAO,EAAE,CAAC;oBACvD,CAAC,EAAE,CAAC;oBAAC,EAAE,IAAI,CAAC,CAAC;oBAAC,EAAE,IAAI,CAAC,CAAC;oBAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;oBAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;oBAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC/D,CAAC;YACD,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACrG,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;YACjH,KAAK,EAAE,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACvE,OAAO,EAAE,cAAc,EAAE,GAAG,GAAG,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,EAAE,eAAe,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC;AAC9H,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,KAAa,EAAE,KAAa,EAAE,GAAW,EAAE,GAAG,GAAG,EAAE;IAClG,IAAI,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,GAAG,KAAK,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACpI,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,GAAG,KAAK,UAAU,KAAK,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC;IAC3D,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACxF,IAAI,MAAM,EAAE,GAAG,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC,KAAoB,CAAC;IAC5D,MAAM,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,qBAAqB,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,KAAK,IAAI,MAAM,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACtU,IAAI,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAA4B,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,GAAG,EAAE,EAAE,YAAY,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC;QACtH,MAAM,IAAI,GAAG,IAAI,GAAG,EAAgD,CAAC;QACrE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACvI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;YAC1C,OAAO,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBACzC,IAAI,UAAU,GAAG,CAAC,CAAC;gBAAC,KAAK,MAAM,KAAK,IAAI,KAAK;oBAAE,UAAU,IAAI,KAAK,CAAC;gBACnE,IAAI,UAAU,GAAG,KAAK,GAAG,CAAC;oBAAE,UAAU,EAAE,CAAC;gBACzC,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;oBACrD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;oBACzD,IAAI,CAAC,CAAC,cAAc,GAAG,KAAK;wBAAE,YAAY,EAAE,CAAC;oBAC7C,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;wBAClB,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;wBACjF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,KAAK,GAAG,KAAK;4BAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;oBAC1F,CAAC;gBACH,CAAC;gBACD,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAAC,KAAK,EAAE,CAAC;YAC5E,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;YACvB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,MAAM,IAAI,oBAAoB,EAAE,CAAC,CAAC,CAAC;YAC5H,MAAM,QAAQ,GAAa,EAAE,CAAC;YAC9B,IAAI,UAAU,GAAG,KAAK,GAAG,GAAG;gBAAE,QAAQ,CAAC,IAAI,CAAC,0FAA0F,CAAC,CAAC;YACxI,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG;gBAAE,QAAQ,CAAC,IAAI,CAAC,+HAA+H,CAAC,CAAC;YAChM,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;YAC7H,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAG,KAAK,IAAI,UAAU,EAAE,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAqB,EAAE,MAAc,EAAE,KAAa,EAAE,SAAiB,EAAE,OAA2H;IACtO,IAAI,OAAO,CAAC,OAAO,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAC3I,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAyG,EAAE,CAAC;IACtH,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QACrD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9F,MAAM,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,GAAG,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACvE,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,GAAG,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QACzI,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,EAAE,aAAa,EAAE,OAAO,CAAC,WAAW,GAAG,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAC/J,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YACjE,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;YAC1G,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAC;YACvI,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtF,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QACtG,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QACzE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,QAAQ,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,gBAAgB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,gCAAgC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC;IACxgB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,aAAa,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,mJAAmJ,EAAE,CAAC;IACtQ,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzF,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,2bAA2b,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC,MAAM,qBAAqB,MAAM,CAAC,IAAI,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAC1lB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzF,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvQ,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACrJ,OAAO,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,CAAC;AAChK,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { CommandRunner } from "./process-runner.js";
2
+ import type { WindowInfo } from "./types.js";
3
+ export declare function listWindows(runner: CommandRunner, platform?: NodeJS.Platform): Promise<WindowInfo[]>;
4
+ //# sourceMappingURL=windows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmC7C,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,UAAU,EAAE,CAAC,CA2BvB"}
@@ -0,0 +1,60 @@
1
+ const WINDOWS_SCRIPT = `
2
+ [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false
3
+ Add-Type -TypeDefinition @'
4
+ using System;
5
+ using System.Text;
6
+ using System.Collections.Generic;
7
+ using System.Runtime.InteropServices;
8
+ public class ClipWindow { public string title; public string hwnd; public string process; public int pid; public bool minimized; }
9
+ public static class ClipWindows {
10
+ delegate bool Callback(IntPtr h, IntPtr p);
11
+ [DllImport("user32.dll")] static extern bool EnumWindows(Callback callback, IntPtr p);
12
+ [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
13
+ [DllImport("user32.dll")] static extern bool IsIconic(IntPtr h);
14
+ [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder text, int count);
15
+ [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
16
+ public static List<ClipWindow> All() {
17
+ var result = new List<ClipWindow>();
18
+ EnumWindows((h,p) => {
19
+ if (!IsWindowVisible(h)) return true;
20
+ var text = new StringBuilder(2048); GetWindowText(h, text, text.Capacity);
21
+ if (text.Length == 0) return true;
22
+ uint pid; GetWindowThreadProcessId(h, out pid);
23
+ string process = ""; try { process = System.Diagnostics.Process.GetProcessById((int)pid).ProcessName; } catch {}
24
+ result.Add(new ClipWindow { title=text.ToString(), hwnd=h.ToInt64().ToString(), process=process, pid=(int)pid, minimized=IsIconic(h) });
25
+ return true;
26
+ }, IntPtr.Zero);
27
+ return result;
28
+ }
29
+ }
30
+ '@
31
+ @([ClipWindows]::All()) | Sort-Object title | ConvertTo-Json -Compress
32
+ `;
33
+ export async function listWindows(runner, platform = process.platform) {
34
+ if (platform === "linux") {
35
+ const result = await runner.run("wmctrl", ["-lp"]);
36
+ if (result.code)
37
+ throw new Error("Install wmctrl for X11 window discovery, or use xwininfo and provide windowId directly.");
38
+ return result.stdout.split(/\r?\n/).flatMap(line => {
39
+ const m = /^(0x[\da-f]+)\s+-?\d+\s+(\d+)\s+\S+\s+(.+)$/i.exec(line);
40
+ return m ? [{ hwnd: m[1], title: m[3], pid: Number(m[2]), process: "X11", windowId: m[1] }] : [];
41
+ });
42
+ }
43
+ if (platform !== "win32")
44
+ throw new Error("Use list_sources to select a macOS display. Window-only discovery is available on Windows and X11.");
45
+ const result = await runner.run("powershell.exe", [
46
+ "-NoLogo",
47
+ "-NoProfile",
48
+ "-NonInteractive",
49
+ "-Command",
50
+ WINDOWS_SCRIPT,
51
+ ]);
52
+ if (result.code !== 0) {
53
+ throw new Error(`Unable to list windows: ${result.stderr.trim()}`);
54
+ }
55
+ if (!result.stdout.trim())
56
+ return [];
57
+ const parsed = JSON.parse(result.stdout);
58
+ return (Array.isArray(parsed) ? parsed : [parsed]).filter((window) => Boolean(window.title && window.hwnd));
59
+ }
60
+ //# sourceMappingURL=windows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows.js","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BtB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAqB,EACrB,QAAQ,GAAoB,OAAO,CAAC,QAAQ;IAE5C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;QAC5H,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACjD,MAAM,CAAC,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtG,CAAC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IAEhJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE;QAChD,SAAS;QACT,YAAY;QACZ,iBAAiB;QACjB,UAAU;QACV,cAAc;KACf,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,MAAM,GAA8B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACpE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CACvD,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CACjD,CAAC;AACJ,CAAC"}
Binary file
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "clipqa-mcp",
3
+ "version": "0.2.1",
4
+ "description": "Background window recording and frame-level QA evidence for AI agents via MCP.",
5
+ "type": "module",
6
+ "bin": {
7
+ "clipqa-mcp": "build/index.js"
8
+ },
9
+ "main": "build/index.js",
10
+ "types": "build/index.d.ts",
11
+ "files": [
12
+ "build",
13
+ "scripts/install-wgc.ps1",
14
+ "docs/assets/banner.png",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "check": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "benchmark": "tsx benchmarks/temporal-coverage.ts",
27
+ "setup:wgc": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-wgc.ps1",
28
+ "start": "node build/index.js",
29
+ "prepack": "npm run build && npm test"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "model-context-protocol",
34
+ "qa",
35
+ "screen-recording",
36
+ "roblox",
37
+ "computer-vision",
38
+ "testing"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/WheresRace/clipqa-mcp.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/WheresRace/clipqa-mcp/issues"
47
+ },
48
+ "homepage": "https://github.com/WheresRace/clipqa-mcp#readme",
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "provenance": false
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/server": "^2.0.0",
55
+ "ffmpeg-static": "^5.3.0",
56
+ "ffprobe-static": "^3.1.0",
57
+ "sharp": "^0.35.4",
58
+ "zod": "^4.6.5"
59
+ },
60
+ "devDependencies": {
61
+ "@modelcontextprotocol/client": "^2.0.0",
62
+ "@types/node": "^24.0.0",
63
+ "tsx": "^4.20.0",
64
+ "typescript": "^7.0.2",
65
+ "vitest": "^5.0.0"
66
+ }
67
+ }
@@ -0,0 +1,40 @@
1
+ # Optional Windows setup. Recording itself never downloads software.
2
+ $ErrorActionPreference = 'Stop'
3
+ $ProgressPreference = 'SilentlyContinue'
4
+ $clipInstall = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.clipqa\bin'
5
+ $clipTemp = Join-Path ([System.IO.Path]::GetTempPath()) ('clipqa-ffmpeg-' + [guid]::NewGuid())
6
+ New-Item -ItemType Directory -Path $clipTemp | Out-Null
7
+ try {
8
+ $clipArchive = Join-Path $clipTemp 'ffmpeg.zip'
9
+ $clipUrl = 'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip'
10
+ Invoke-WebRequest -UseBasicParsing -Uri $clipUrl -OutFile $clipArchive -TimeoutSec 120
11
+ $clipExpected = ((Invoke-WebRequest -UseBasicParsing -Uri ($clipUrl + '.sha256') -TimeoutSec 30).Content.Trim() -split '\s+')[0]
12
+ $clipHasher = [Security.Cryptography.SHA256]::Create()
13
+ $clipStream = [IO.File]::OpenRead($clipArchive)
14
+ try { $clipActual = [BitConverter]::ToString($clipHasher.ComputeHash($clipStream)).Replace('-', '') }
15
+ finally { $clipStream.Dispose(); $clipHasher.Dispose() }
16
+ if ($clipExpected -notmatch '^[0-9a-fA-F]{64}$' -or $clipActual -ne $clipExpected) {
17
+ throw 'FFmpeg checksum mismatch. No binaries installed; retry in case the release changed during download.'
18
+ }
19
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
20
+ [IO.Compression.ZipFile]::ExtractToDirectory($clipArchive, (Join-Path $clipTemp 'extracted'))
21
+ $clipExe = Get-ChildItem -LiteralPath (Join-Path $clipTemp 'extracted') -Recurse -Filter ffmpeg.exe | Select-Object -First 1
22
+ if (-not $clipExe) { throw 'FFmpeg executable missing from archive.' }
23
+ $ErrorActionPreference = 'Continue'
24
+ $clipHelp = & $clipExe.FullName -hide_banner -h filter=gfxcapture 2>&1 | Out-String
25
+ $clipExit = $LASTEXITCODE
26
+ $ErrorActionPreference = 'Stop'
27
+ if ($clipExit -ne 0) { throw 'Downloaded FFmpeg could not run.' }
28
+ if ($clipHelp -notmatch 'capture_cursor' -or $clipHelp -notmatch 'hwnd') { throw 'Downloaded build lacks the required gfxcapture filter.' }
29
+ New-Item -ItemType Directory -Path $clipInstall -Force | Out-Null
30
+ Copy-Item -LiteralPath $clipExe.FullName -Destination (Join-Path $clipInstall 'ffmpeg.exe') -Force
31
+ Copy-Item -LiteralPath (Join-Path $clipExe.DirectoryName 'ffprobe.exe') -Destination (Join-Path $clipInstall 'ffprobe.exe') -Force
32
+ Get-ChildItem -LiteralPath $clipExe.Directory.Parent.FullName -File | Where-Object { $_.Name -match 'LICENSE|README' } | Copy-Item -Destination $clipInstall -Force
33
+ Write-Output "Installed verified WGC-capable FFmpeg in $clipInstall. Restart your MCP client to use it. FFmpeg is separately licensed by its distributor."
34
+ } finally {
35
+ $clipResolved = [IO.Path]::GetFullPath($clipTemp)
36
+ $clipRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
37
+ if ($clipResolved.StartsWith($clipRoot, [StringComparison]::OrdinalIgnoreCase) -and [IO.Path]::GetFileName($clipResolved).StartsWith('clipqa-ffmpeg-')) {
38
+ Remove-Item -LiteralPath $clipResolved -Recurse -Force
39
+ }
40
+ }